[
  {
    "path": ".github/CODEOWNERS",
    "content": "# CODEOWNERS file indicates code owners for certain files\n#\n# Code owners will automatically be added as a reviewer for PRs that touch\n# the owned files.\n#\n\n# Default owners for everything in the repo\n#\n# Unless a later match takes precedence, these owners will be requested for\n# review when someone opens a pull request.\n\n/.github/settings.yml @k8sgpt-ai/maintainers\n* @k8sgpt-ai/maintainers @k8sgpt-ai/k8sgpt-maintainers @k8sgpt-ai/k8sgpt-approvers\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!-- \nThanks for creating this pull request 🤗\n\nPlease make sure that the pull request is limited to one type (docs, feature, etc.) and keep it as small as possible. You can open multiple prs instead of opening a huge one.\n-->\n\n<!-- If this pull request closes an issue, please mention the issue number below -->\nCloses # <!-- Issue # here -->\n\n## 📑 Description\n<!-- Add a brief description of the pr -->\n\n## ✅ Checks\n<!-- Make sure your pr passes the CI checks and do check the following fields as needed - -->\n- [ ] My pull request adheres to the code style of this project\n- [ ] My code requires changes to the documentation\n- [ ] I have updated the documentation as required\n- [ ] All the tests have passed\n\n## ℹ Additional Information\n<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->"
  },
  {
    "path": ".github/settings.yml",
    "content": "repository:\n  name: \"k8sgpt\"\n  description: \"Giving Kubernetes SRE superpowers to everyone\"\n  homepage_url: \"https://k8sgpt.ai\"\n  topics: kubernetes, devops, tooling, openai, sre\n\n  default_branch: main\n  allow_squash_merge: true\n  allow_merge_commit: true\n  allow_rebase_merge: true\n\n  has_wiki: false\n\n  teams:\n    - name: \"maintainers\"\n      permission: \"admin\"\n    - name: \"k8sgpt-maintainers\"\n      permission: \"maintain\"\n    - name: \"k8sgpt-approvers\"\n      permission: \"push\"\n    - name: \"contributors\"\n      permission: \"push\"\n\nbranches:\n  - name: main\n    protection:\n      required_pull_request_reviews:\n        required_approving_review_count: 1\n        dismiss_stale_reviews: true\n        require_code_owner_reviews: true\n        dismissal_restrictions: {}\n        code_owner_approval: true\n        required_conversation_resolution: true\n\n      required_status_checks:\n        strict: true\n        contexts:\n          - \"DCO\"\n\n      enforce_admins: true\n\n      required_linear_history: true\n\n      restrictions:\n        users: []\n        apps: []\n        teams: []"
  },
  {
    "path": ".github/workflows/build_container.yaml",
    "content": "name: Build container\n\non:\n  push:\n    branches:\n      - 'main'\n      - '[0-9]+.[1-9][0-9]*.x'\n  pull_request:\n    branches:\n      - 'main'\n      - fix/build-branch\n      - '[0-9]+.[1-9][0-9]*.x'\n    paths-ignore:\n      - \"**.md\"\n\nenv:\n  GO_VERSION: \"~1.24\"\n  IMAGE_NAME: \"k8sgpt\"\n  REGISTRY_IMAGE: ghcr.io/k8sgpt-ai/k8sgpt\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  prepare_ci_run:\n    name: Prepare CI Run\n    runs-on: ubuntu-latest\n    outputs:\n      GIT_SHA: ${{ steps.extract_branch.outputs.GIT_SHA }}\n      BRANCH: ${{ steps.extract_branch.outputs.BRANCH }}\n      BRANCH_SLUG: ${{ steps.extract_branch.outputs.BRANCH_SLUG }}\n      DATETIME: ${{ steps.get_datetime.outputs.DATETIME }}\n      BUILD_TIME: ${{ steps.get_datetime.outputs.BUILD_TIME }}\n      NON_FORKED_AND_NON_ROBOT_RUN: ${{ steps.get_run_type.outputs.NON_FORKED_AND_NON_ROBOT_RUN }}\n\n    steps:\n      - name: Check out code\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: Extract branch name\n        id: extract_branch\n        uses: keptn/gh-action-extract-branch-name@main\n\n      - name: Get current date and time\n        id: get_datetime\n        run: |\n          DATETIME=$(date +'%Y%m%d%H%M')\n          BUILD_TIME=$(date -u \"+%F_%T\")\n          echo \"DATETIME=$DATETIME\" >> \"$GITHUB_OUTPUT\"\n          echo \"BUILD_TIME=$BUILD_TIME\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Get workflow run type\n        id: get_run_type\n        run: |\n          NON_FORKED_AND_NON_ROBOT_RUN=${{ ( github.actor != 'renovate[bot]' && github.actor != 'dependabot[bot]' ) && ( github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository ) }}\n          echo \"NON_FORKED_AND_NON_ROBOT_RUN=$NON_FORKED_AND_NON_ROBOT_RUN\" >> \"$GITHUB_OUTPUT\"\n\n  build-and-push:\n    name: Build and Push Multi-arch Image\n    needs: prepare_ci_run\n    runs-on: ubuntu-latest\n    if: ${{ needs.prepare_ci_run.outputs.NON_FORKED_AND_NON_ROBOT_RUN == 'true' }}\n    env:\n      DATETIME: ${{ needs.prepare_ci_run.outputs.DATETIME }}\n      BUILD_TIME: ${{ needs.prepare_ci_run.outputs.BUILD_TIME }}\n      GIT_SHA: ${{ needs.prepare_ci_run.outputs.GIT_SHA }}\n\n    steps:\n      - name: Check out code\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: Docker meta\n        id: meta\n        uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5\n        with:\n          images: ${{ env.REGISTRY_IMAGE }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n            type=raw,value=dev-${{ env.DATETIME }}\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.K8SGPT_BOT_SECRET }}\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3\n\n      - name: Build and push multi-arch image\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6\n        with:\n          context: .\n          file: ./container/Dockerfile\n          platforms: linux/amd64,linux/arm64\n          push: true\n          target: production\n          build-args: |\n            GIT_HASH=${{ env.GIT_SHA }}\n            RELEASE_VERSION=dev-${{ env.DATETIME }}\n            BUILD_TIME=${{ env.BUILD_TIME }}\n          tags: |\n            ${{ env.REGISTRY_IMAGE }}:${{ env.DATETIME }}\n          labels: ${{ steps.meta.outputs.labels }}\n          secrets: |\n            GIT_AUTH_TOKEN=${{ secrets.K8SGPT_BOT_SECRET }}\n"
  },
  {
    "path": ".github/workflows/golangci_lint.yaml",
    "content": "name: Run golangci-lint\n\non:\n  pull_request:\n    branches: [main]\n\njobs:\n  golangci-lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8\n        with:\n          version: v2.1.0\n          only-new-issues: true"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: release\n\non:\n  push:\n    branches:\n      - main\n      - '[0-9]+.[0-9]+.x'\n  workflow_dispatch:\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  release-please:\n    permissions:\n      contents: write  # for google-github-actions/release-please-action to create release commit\n      pull-requests: write  # for google-github-actions/release-please-action to create release PR\n    runs-on: ubuntu-latest\n    outputs:\n      releases_created: ${{ steps.release.outputs.releases_created }}\n      tag_name: ${{ steps.release.outputs.tag_name }}\n    # Release-please creates a PR that tracks all changes\n    steps:\n      - name: Checkout\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n     \n      - uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4.1.1\n        id: release\n        with:\n          command: manifest\n          token: ${{secrets.GITHUB_TOKEN}}\n          default-branch: main\n\n  goreleaser:\n    if: needs.release-please.outputs.releases_created == 'true'\n    permissions:\n      contents: write\n    needs:\n      - release-please\n    runs-on: ubuntu-latest\n    steps:\n      - name: Free Disk Space (Ubuntu)\n        uses: jlumbroso/free-disk-space@main\n        with:\n          # this might remove tools that are actually needed,\n          # if set to \"true\" but frees about 6 GB\n          tool-cache: false\n          # all of these default to true, but feel free to set to\n          # \"false\" if necessary for your workflow\n          android: false\n          dotnet: false\n          haskell: false\n          large-packages: true\n          docker-images: true\n          swap-storage: true\n      - name: Checkout\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n        with:\n          fetch-depth: 0\n      - name: Set up Go\n        uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5\n        with:\n          go-version: '~1.24'\n      - name: Download Syft\n        uses: anchore/sbom-action/download-syft@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6\n        with:\n          # either 'goreleaser' (default) or 'goreleaser-pro'\n          distribution: goreleaser\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.K8SGPT_BOT_SECRET }}\n          SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}\n#      - name: Update new version in krew-index\n#        uses: rajatjindal/krew-release-bot@3d9faef30a82761d610544f62afddca00993eef9 # v0.0.47\n\n  build-container:\n    if: needs.release-please.outputs.releases_created == 'true'\n    needs:\n      - release-please\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: write\n      id-token: write\n    env:\n      IMAGE_TAG: ghcr.io/k8sgpt-ai/k8sgpt:${{ needs.release-please.outputs.tag_name }}\n      IMAGE_NAME: k8sgpt\n    steps:\n      - name: Checkout\n        uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n        with:\n          submodules: recursive\n\n      - name: Set up Docker Buildx\n        id: buildx\n        uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3\n        with:\n          registry: \"ghcr.io\"\n          username: ${{ github.actor }}\n          password: ${{ secrets.K8SGPT_BOT_SECRET }}\n\n      - name: Build Docker Image\n        uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6\n        with:\n          context: .\n          file: ./container/Dockerfile\n          platforms: linux/amd64,linux/arm64\n          target: production\n          tags: |\n            ${{ env.IMAGE_TAG }}\n          builder: ${{ steps.buildx.outputs.name }}\n          push: true\n          cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}\n          cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}\n\n      - name: Generate SBOM\n        uses: anchore/sbom-action@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8\n        with:\n          image: ${{ env.IMAGE_TAG }}\n          artifact-name: sbom-${{ env.IMAGE_NAME }}\n          output-file: ./sbom-${{ env.IMAGE_NAME }}.spdx.json\n\n      - name: Attach SBOM to release\n        uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2\n        with:\n          tag_name: ${{ needs.release-please.outputs.tag_name }}\n          files: ./sbom-${{ env.IMAGE_NAME }}.spdx.json\n"
  },
  {
    "path": ".github/workflows/semantic_pr.yaml",
    "content": "name: Semantic PR Validation\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\ndefaults:\n  run:\n    shell: bash\njobs:\n  validate:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read # Needed for checking out the repository\n      pull-requests: read # Needed for reading prs\n    steps:\n      - name: Validate Pull Request\n        uses: amannn/action-semantic-pull-request@fdd4d3ddf614fbcd8c29e4b106d3bbe0cb2c605d # v6.0.1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          # Configure which types are allowed.\n          # Default: https://github.com/commitizen/conventional-commit-types\n          types: |\n            feat\n            fix\n            build\n            chore\n            ci\n            docs\n            perf\n            refactor\n            revert\n            style\n            test\n            deps\n          scopes: |\n            deps\n          # Configure that a scope must always be provided.\n          requireScope: false\n          # When using \"Squash and merge\" on a PR with only one commit, GitHub\n          # will suggest using that commit message instead of the PR title for the\n          # merge commit, and it's easy to commit this by mistake. Enable this option\n          # to also validate the commit message for one commit PRs.\n          validateSingleCommit: true\n          # Configure additional validation for the subject based on a regex.\n          # This ensures the subject doesn't start with an uppercase character.\n          subjectPattern: ^(?![A-Z]).+$\n          # If `subjectPattern` is configured, you can use this property to override\n          # the default error message that is shown when the pattern doesn't match.\n          # The variables `subject` and `title` can be used within the message.\n          subjectPatternError: |\n            The subject \"{subject}\" found in the pull request title \"{title}\"\n            didn't match the configured pattern. Please ensure that the subject\n            doesn't start with an uppercase character."
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Run tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\nenv:\n  GO_VERSION: \"~1.24\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5\n\n      - name: Set up Go\n        uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5\n        with:\n          go-version: ${{ env.GO_VERSION }}\n\n      - name: Run test\n        run: go test ./... -coverprofile=coverage.txt\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n__debug*\n.DS_Store\nk8sgpt*\n!charts/k8sgpt\n*.vscode\ndist/\n\nbin/\npkg/server/example/example"
  },
  {
    "path": ".goreleaser.yaml",
    "content": "version: 2\n# This is an example .goreleaser.yml file with some sensible defaults.\n# Make sure to check the documentation at https://goreleaser.com\nbefore:\n  hooks:\n    # You may remove this if you don't use go modules.\n    - go mod tidy\n    # you may remove this if you don't need go generate\n    - go generate ./...\nbuilds:\n  - env:\n      - CGO_ENABLED=0\n    goos:\n      - linux\n      - windows\n      - darwin\n    ldflags:\n      - -s -w\n      - -X main.version={{.Version}}\n      - -X main.commit={{.ShortCommit}}\n      - -X main.Date={{.CommitDate}}\n\nnfpms:\n  - file_name_template: \"{{ .ProjectName }}_{{ .Arch }}\"\n    maintainer: \"K8sGPT Maintainers <contact@k8sgpt.ai>\"\n    homepage: https://k8sgpt.ai\n    description: >-\n      K8sGPT is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english. It has SRE experience codified into it’s analyzers and helps to pull out the most relevant information to enrich it with AI.\n    license: \"Apache-2.0\"\n    formats:\n      - deb\n      - rpm\n      - apk\n    bindir: /usr/bin\n    section: utils\n    contents:\n      - src: ./LICENSE\n        dst: /usr/share/doc/k8sgpt/copyright\n        file_info:\n          mode: 0644\n\nsboms:\n  - artifacts: archive\n\narchives:\n  - format: tar.gz\n    # this name template makes the OS and Arch compatible with the results of uname.\n    name_template: >-\n      {{ .ProjectName }}_\n      {{- title .Os }}_\n      {{- if eq .Arch \"amd64\" }}x86_64\n      {{- else if eq .Arch \"386\" }}i386\n      {{- else }}{{ .Arch }}{{ end }}\n      {{- if .Arm }}v{{ .Arm }}{{ end }}\n    # use zip for windows archives\n    format_overrides:\n      - goos: windows\n        format: zip\n\nbrews:\n  - name: k8sgpt\n    homepage: https://k8sgpt.ai\n    repository:\n      owner: k8sgpt-ai\n      name: homebrew-k8sgpt\n\nchecksum:\n  name_template: \"checksums.txt\"\n\nsnapshot:\n  name_template: \"{{ incpatch .Version }}-next\"\n\nannounce:\n  slack:\n    # Whether its enabled or not.\n    #\n    # Templates: allowed (since v2.6).\n    enabled: true\n\n    # Message template to use while publishing.\n    #\n    # Default: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'.\n    # Templates: allowed.\n    message_template: \"{{ .ProjectName }} release {{.Tag}} is out!\"\n\n    # The name of the channel that the user selected as a destination for webhook messages.\n    channel: \"#general\"\n\n    # Set your Webhook's user name.\n    username: \"K8sGPT\"\n\n    # Emoji to use as the icon for this message. Overrides icon_url.\n    icon_emoji: \"\"\n\n    # URL to an image to use as the icon for this message.\n    icon_url: \"\"\n\n"
  },
  {
    "path": ".krew.yaml",
    "content": "apiVersion: krew.googlecontainertools.github.com/v1alpha2\nkind: Plugin\nmetadata:\n  name: gpt\nspec:\n  version: {{ .TagName }}\n  homepage: https://github.com/k8sgpt-ai/k8sgpt\n  shortDescription: \"Giving Kubernetes Superpowers to everyone\"\n  description: |\n    A tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.\n  platforms:\n    ##########\n    # Darwin #\n    ##########\n    - selector:\n        matchLabels:\n          os: darwin\n          arch: amd64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_x86_64.tar.gz\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n    - selector:\n        matchLabels:\n          os: darwin\n          arch: arm64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_arm64.tar.gz\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n\n    #########\n    # Linux #\n    #########\n    - selector:\n        matchLabels:\n          os: linux\n          arch: amd64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_x86_64.tar.gz\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n    - selector:\n        matchLabels:\n          os: linux\n          arch: arm64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_arm64.tar.gz\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n    - selector:\n        matchLabels:\n          os: linux\n          arch: \"386\"\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_i386.tar.gz\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n\n    ###########\n    # Windows #\n    ###########\n    - selector:\n        matchLabels:\n          os: windows\n          arch: amd64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_x86_64.zip\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n    - selector:\n        matchLabels:\n          os: windows\n          arch: arm64\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_arm64.zip\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n    - selector:\n        matchLabels:\n          os: windows\n          arch: \"386\"\n      {{addURIAndSha \"https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_i386.zip\" .TagName | indent 6 }}\n      files:\n        - from: \"k8sgpt\"\n          to: \"kubectl-gpt\"\n        - from: \"LICENSE\"\n          to: \".\"\n      bin: kubectl-gpt\n"
  },
  {
    "path": ".release-please-manifest.json",
    "content": "{\".\":\"0.4.30\"}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [0.4.30](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.29...v0.4.30) (2026-02-20)\n\n\n### Bug Fixes\n\n* validate namespace before running custom analyzers ([#1617](https://github.com/k8sgpt-ai/k8sgpt/issues/1617)) ([458aa9d](https://github.com/k8sgpt-ai/k8sgpt/commit/458aa9debac7590eb0855ffd12141b702e999a36))\n\n## [0.4.29](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.28...v0.4.29) (2026-02-20)\n\n\n### Features\n\n* **serve:** add short flag and env var for metrics port ([#1616](https://github.com/k8sgpt-ai/k8sgpt/issues/1616)) ([4f63e97](https://github.com/k8sgpt-ai/k8sgpt/commit/4f63e9737c6a2306686bd3b6f37e81f210665949))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to b8788ab ([#1572](https://github.com/k8sgpt-ai/k8sgpt/issues/1572)) ([a56e478](https://github.com/k8sgpt-ai/k8sgpt/commit/a56e4788c3361a64df17175f163f33422a8fe606))\n* use proper JSON marshaling for customrest prompt to handle special characters ([#1615](https://github.com/k8sgpt-ai/k8sgpt/issues/1615)) ([99911fb](https://github.com/k8sgpt-ai/k8sgpt/commit/99911fbb3ac8c950fd7ee1b3210f8a9c2a6b0ad7)), closes [#1556](https://github.com/k8sgpt-ai/k8sgpt/issues/1556)\n\n\n### Refactoring\n\n* improve MCP server handlers with better error handling and pagination ([#1613](https://github.com/k8sgpt-ai/k8sgpt/issues/1613)) ([abc4647](https://github.com/k8sgpt-ai/k8sgpt/commit/abc46474e372bcd27201f1a64372c04269acee13))\n\n## [0.4.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.27...v0.4.28) (2026-02-15)\n\n\n### Features\n\n* add Groq as LLM provider ([#1600](https://github.com/k8sgpt-ai/k8sgpt/issues/1600)) ([867bce1](https://github.com/k8sgpt-ai/k8sgpt/commit/867bce1907f5dd3387128b72c694e98091d55554))\n* multiple security fixes. Prometheus: v0.302.1 → v0.306.0 ([#1597](https://github.com/k8sgpt-ai/k8sgpt/issues/1597)) ([f5fb2a7](https://github.com/k8sgpt-ai/k8sgpt/commit/f5fb2a7e12e14fad8107940aeead5e60b064add1))\n\n\n### Bug Fixes\n\n* align CI Go versions with go.mod to ensure consistency ([#1611](https://github.com/k8sgpt-ai/k8sgpt/issues/1611)) ([1f2ff98](https://github.com/k8sgpt-ai/k8sgpt/commit/1f2ff988342b8ef2aa3e3263eb845c0ee09fe24c))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1550](https://github.com/k8sgpt-ai/k8sgpt/issues/1550)) ([7fe3bdb](https://github.com/k8sgpt-ai/k8sgpt/commit/7fe3bdbd952bc9a1975121de5f21ad31dc1f691d))\n* use MaxCompletionTokens instead of deprecated MaxTokens for OpenAI ([#1604](https://github.com/k8sgpt-ai/k8sgpt/issues/1604)) ([c80b2e2](https://github.com/k8sgpt-ai/k8sgpt/commit/c80b2e2c346845336593ce515fe90fd501b1d0a7))\n\n\n### Other\n\n* **deps:** update actions/checkout digest to 93cb6ef ([#1592](https://github.com/k8sgpt-ai/k8sgpt/issues/1592)) ([40ffcbe](https://github.com/k8sgpt-ai/k8sgpt/commit/40ffcbec6b65e3a99e40be5f414a3f2c087bffbb))\n* **deps:** update actions/setup-go digest to 40f1582 ([#1593](https://github.com/k8sgpt-ai/k8sgpt/issues/1593)) ([a303ffa](https://github.com/k8sgpt-ai/k8sgpt/commit/a303ffa21c7ede3dd9391185bc91fb3b4e8276b6))\n* util tests ([#1594](https://github.com/k8sgpt-ai/k8sgpt/issues/1594)) ([21369c5](https://github.com/k8sgpt-ai/k8sgpt/commit/21369c5c0917fd2b6ae4173378b2e257e2b1de7b))\n\n## [0.4.27](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.26...v0.4.27) (2025-12-18)\n\n\n### Features\n\n* mcp v2 ([#1589](https://github.com/k8sgpt-ai/k8sgpt/issues/1589)) ([5480051](https://github.com/k8sgpt-ai/k8sgpt/commit/5480051230ce83b89c0382abd7992c7ecc4a85b8))\n\n## [0.4.26](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.25...v0.4.26) (2025-10-16)\n\n\n### Other\n\n* missing filter arg on serve ([#1583](https://github.com/k8sgpt-ai/k8sgpt/issues/1583)) ([f1d2e30](https://github.com/k8sgpt-ai/k8sgpt/commit/f1d2e306f32eb1e01a2788174084be29a7fa1282))\n\n## [0.4.25](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.24...v0.4.25) (2025-09-03)\n\n\n### Features\n\n* fix to broken inference ([#1575](https://github.com/k8sgpt-ai/k8sgpt/issues/1575)) ([291e42d](https://github.com/k8sgpt-ai/k8sgpt/commit/291e42dc4b81ffb0672c21fbb325ddebc5d531a3))\n\n## [0.4.24](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.23...v0.4.24) (2025-08-18)\n\n\n### Features\n\n* add ClusterServiceVersion, Subscription, InstallPlan, OperatorGroup, and CatalogSource analyzers ([#1564](https://github.com/k8sgpt-ai/k8sgpt/issues/1564)) ([0cf4cae](https://github.com/k8sgpt-ai/k8sgpt/commit/0cf4cae07e32a0025246abcf2d1a5a91f82d093a))\n* reintroduced inference code ([#1548](https://github.com/k8sgpt-ai/k8sgpt/issues/1548)) ([7e33276](https://github.com/k8sgpt-ai/k8sgpt/commit/7e332761d89d953989b4f33509208dd4db4d4b91))\n* update helm charts with mcp support and fix Google ADA issue  ([#1568](https://github.com/k8sgpt-ai/k8sgpt/issues/1568)) ([5334589](https://github.com/k8sgpt-ai/k8sgpt/commit/53345895deec4c74cac00ee3fd5e230f6a92cf4a))\n\n\n### Bug Fixes\n\n* migrated to more actively maintained mcp golang lib and added AI explain  ([#1557](https://github.com/k8sgpt-ai/k8sgpt/issues/1557)) ([c47ae59](https://github.com/k8sgpt-ai/k8sgpt/commit/c47ae595fb9fc5bf22afef3bc6764b3e87e4553d))\n\n\n### Other\n\n* **deps:** update actions/checkout action to v5 ([#1562](https://github.com/k8sgpt-ai/k8sgpt/issues/1562)) ([e385e77](https://github.com/k8sgpt-ai/k8sgpt/commit/e385e77da93a65fe52a152bf1f8f1415552698d5))\n* **deps:** update amannn/action-semantic-pull-request action to v6 ([#1565](https://github.com/k8sgpt-ai/k8sgpt/issues/1565)) ([c5c9135](https://github.com/k8sgpt-ai/k8sgpt/commit/c5c9135900ec6f95b63dac47df751269e7420e87))\n* **deps:** update docker/login-action digest to 184bdaa ([#1559](https://github.com/k8sgpt-ai/k8sgpt/issues/1559)) ([0239b2f](https://github.com/k8sgpt-ai/k8sgpt/commit/0239b2fe6e7105bbcf3256c559c30ec7065b25f3))\n* **deps:** update goreleaser/goreleaser-action digest to e435ccd ([#1569](https://github.com/k8sgpt-ai/k8sgpt/issues/1569)) ([5e86f49](https://github.com/k8sgpt-ai/k8sgpt/commit/5e86f4925c4209b0eb2959227229c2994cfc5b6f))\n\n## [0.4.23](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.22...v0.4.23) (2025-08-08)\n\n\n### Features\n\n* add ClusterCatalog and ClusterExtension analyzers ([#1555](https://github.com/k8sgpt-ai/k8sgpt/issues/1555)) ([a821814](https://github.com/k8sgpt-ai/k8sgpt/commit/a821814125e25c062ff2faebf9df1b880414c22c))\n* oci genai chat models ([#1337](https://github.com/k8sgpt-ai/k8sgpt/issues/1337)) ([290a4be](https://github.com/k8sgpt-ai/k8sgpt/commit/290a4be210fbb508214070c31218138781d96142))\n\n\n### Bug Fixes\n\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1537](https://github.com/k8sgpt-ai/k8sgpt/issues/1537)) ([50d5d78](https://github.com/k8sgpt-ai/k8sgpt/commit/50d5d78c06e42d75a2448989528e5e6be12ea825))\n* **deps:** update module helm.sh/helm/v3 to v3.17.4 [security] ([#1541](https://github.com/k8sgpt-ai/k8sgpt/issues/1541)) ([5b42249](https://github.com/k8sgpt-ai/k8sgpt/commit/5b4224951e7348e9d78292dadc9b9786957117f1))\n\n## [0.4.22](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.21...v0.4.22) (2025-07-18)\n\n\n### Features\n\n* add APAC region Claude models support for Amazon Bedrock ([#1543](https://github.com/k8sgpt-ai/k8sgpt/issues/1543)) ([1819e6f](https://github.com/k8sgpt-ai/k8sgpt/commit/1819e6f410d078fce2bda8bbdb22054dfb4fc092))\n* add streamable-http support for MCP server ([#1546](https://github.com/k8sgpt-ai/k8sgpt/issues/1546)) ([3a1187a](https://github.com/k8sgpt-ai/k8sgpt/commit/3a1187ad5a190713b9216cf6d9d52d54cdb3e4da))\n\n## [0.4.21](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.20...v0.4.21) (2025-06-27)\n\n\n### Features\n\n* add latest and legacy stable models ([#1539](https://github.com/k8sgpt-ai/k8sgpt/issues/1539)) ([00c0799](https://github.com/k8sgpt-ai/k8sgpt/commit/00c07999e2290e70a6ecb95b255b4924f55ecd5f))\n* support for claude4 && model names listed ([#1540](https://github.com/k8sgpt-ai/k8sgpt/issues/1540)) ([8002d94](https://github.com/k8sgpt-ai/k8sgpt/commit/8002d943453aac8c3675d7072b25dfdc3aec1c1d))\n\n\n### Bug Fixes\n\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1511](https://github.com/k8sgpt-ai/k8sgpt/issues/1511)) ([08f2855](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2855a4d7e61f3422cb68b0966272a85f617a5))\n\n\n### Other\n\n* **deps:** update docker/setup-buildx-action digest to e468171 ([#1527](https://github.com/k8sgpt-ai/k8sgpt/issues/1527)) ([0c917fc](https://github.com/k8sgpt-ai/k8sgpt/commit/0c917fc60115ef0dc775e858a55964382b20c5e1))\n\n## [0.4.20](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.19...v0.4.20) (2025-06-20)\n\n\n### Features\n\n* added cache purge ([#1532](https://github.com/k8sgpt-ai/k8sgpt/issues/1532)) ([74fbde0](https://github.com/k8sgpt-ai/k8sgpt/commit/74fbde00537e627c408b317ff9098227be11e2ad))\n\n\n### Other\n\n* model name ([#1535](https://github.com/k8sgpt-ai/k8sgpt/issues/1535)) ([0f700f0](https://github.com/k8sgpt-ai/k8sgpt/commit/0f700f0cd39bf5881d6c05240b842f4df7a6c016))\n\n## [0.4.19](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.18...v0.4.19) (2025-06-20)\n\n\n### Features\n\n* fixed haiku ([#1530](https://github.com/k8sgpt-ai/k8sgpt/issues/1530)) ([5636515](https://github.com/k8sgpt-ai/k8sgpt/commit/5636515db98b529689a214af5066d50b5e42d3a1))\n\n## [0.4.18](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.17...v0.4.18) (2025-06-20)\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 4c0f3b2 ([#1523](https://github.com/k8sgpt-ai/k8sgpt/issues/1523)) ([7d4cb26](https://github.com/k8sgpt-ai/k8sgpt/commit/7d4cb267130f60088350213482795f37594cb0bc))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1509](https://github.com/k8sgpt-ai/k8sgpt/issues/1509)) ([d7cb19a](https://github.com/k8sgpt-ai/k8sgpt/commit/d7cb19ad29c92eaba552ba723945c937fc3c42da))\n\n\n### Other\n\n* **deps:** update codecov/codecov-action digest to 18283e0 ([#1513](https://github.com/k8sgpt-ai/k8sgpt/issues/1513)) ([42654e7](https://github.com/k8sgpt-ai/k8sgpt/commit/42654e7f55d7a9e9be5b664adaaa8979106e7298))\n* **deps:** update docker/build-push-action digest to 1dc7386 ([#1512](https://github.com/k8sgpt-ai/k8sgpt/issues/1512)) ([dfcc5dc](https://github.com/k8sgpt-ai/k8sgpt/commit/dfcc5dc5a15a3d59a7f6317944784e3ecd86fb50))\n* **deps:** update docker/build-push-action digest to 2634353 ([#1517](https://github.com/k8sgpt-ai/k8sgpt/issues/1517)) ([7dfe8be](https://github.com/k8sgpt-ai/k8sgpt/commit/7dfe8bef0face65f607475a6620923fdfed57961))\n* **deps:** update softprops/action-gh-release digest to 72f2c25 ([#1526](https://github.com/k8sgpt-ai/k8sgpt/issues/1526)) ([5947876](https://github.com/k8sgpt-ai/k8sgpt/commit/5947876e4942729eea883937faf5e2b47d1f16ec))\n* **deps:** update softprops/action-gh-release digest to d5382d3 ([#1525](https://github.com/k8sgpt-ai/k8sgpt/issues/1525)) ([6b9f346](https://github.com/k8sgpt-ai/k8sgpt/commit/6b9f346bf668ed3517b23b99000611ea14afafe2))\n* model access ([#1529](https://github.com/k8sgpt-ai/k8sgpt/issues/1529)) ([be4fb1c](https://github.com/k8sgpt-ai/k8sgpt/commit/be4fb1cc034d9c3843cf3e9912a26e05bd54c146))\n\n## [0.4.17](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.16...v0.4.17) (2025-05-14)\n\n\n### Features\n\n* adding fixes for Messages API issue 1391 ([#1504](https://github.com/k8sgpt-ai/k8sgpt/issues/1504)) ([b2241c0](https://github.com/k8sgpt-ai/k8sgpt/commit/b2241c03c975aeab02897d73e57cd351f60f3af3))\n* new job analyzer ([#1506](https://github.com/k8sgpt-ai/k8sgpt/issues/1506)) ([0b7ddf5](https://github.com/k8sgpt-ai/k8sgpt/commit/0b7ddf5e3b93e56ea92dfb6447e97c067cad9e54))\n\n\n### Bug Fixes\n\n* align documentation to reflect default analyzers properly ([#1498](https://github.com/k8sgpt-ai/k8sgpt/issues/1498)) ([7e375a3](https://github.com/k8sgpt-ai/k8sgpt/commit/7e375a30bee24198f9221e4a4aea17fcd2fe005c))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1454](https://github.com/k8sgpt-ai/k8sgpt/issues/1454)) ([d0f0364](https://github.com/k8sgpt-ai/k8sgpt/commit/d0f03641ae372a00cd0eca1f41ef30a988d436bc))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1500](https://github.com/k8sgpt-ai/k8sgpt/issues/1500)) ([d308c51](https://github.com/k8sgpt-ai/k8sgpt/commit/d308c511fbe06e012c641dfa08c4dcf4181b243a))\n* panic in k8sgpt auth update ([#1497](https://github.com/k8sgpt-ai/k8sgpt/issues/1497)) ([cae94e7](https://github.com/k8sgpt-ai/k8sgpt/commit/cae94e7b6df1684a3b61af3e7aa0f4e68e8df594))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to d35c59a ([#1495](https://github.com/k8sgpt-ai/k8sgpt/issues/1495)) ([e76bdb0](https://github.com/k8sgpt-ai/k8sgpt/commit/e76bdb0c23b7d23972d99661c8fe1bffe5f9f398))\n* **deps:** update golangci/golangci-lint-action action to v8 ([#1490](https://github.com/k8sgpt-ai/k8sgpt/issues/1490)) ([1e57b77](https://github.com/k8sgpt-ai/k8sgpt/commit/1e57b7774c20bda4ae0b0d765278bcd3504cfb33))\n* golangci lint ([#1508](https://github.com/k8sgpt-ai/k8sgpt/issues/1508)) ([4faf77d](https://github.com/k8sgpt-ai/k8sgpt/commit/4faf77d91a3da8fdd6166ec1c381a151e5846057))\n\n## [0.4.16](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.15...v0.4.16) (2025-05-06)\n\n\n### Features\n\n* add support for Amazon Bedrock Inference Profiles ([#1492](https://github.com/k8sgpt-ai/k8sgpt/issues/1492)) ([21bc76e](https://github.com/k8sgpt-ai/k8sgpt/commit/21bc76e5b77524b48f09ef6707204742dcd879a7))\n* enhancement of deployment analyzer ([#1406](https://github.com/k8sgpt-ai/k8sgpt/issues/1406)) ([61b60d5](https://github.com/k8sgpt-ai/k8sgpt/commit/61b60d5768b54f98232dcc415e89aa38987dc6e3))\n* supported regions govcloud ([#1483](https://github.com/k8sgpt-ai/k8sgpt/issues/1483)) ([752a16c](https://github.com/k8sgpt-ai/k8sgpt/commit/752a16c40728f42f10ab6c3177cb7e24f44db339))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 0f33e8f ([#1484](https://github.com/k8sgpt-ai/k8sgpt/issues/1484)) ([6a81d2c](https://github.com/k8sgpt-ai/k8sgpt/commit/6a81d2c140f00a405b651d6c6dae5e343ffddb4f))\n\n\n### Other\n\n* **deps:** update docker/build-push-action digest to 14487ce ([#1472](https://github.com/k8sgpt-ai/k8sgpt/issues/1472)) ([81da402](https://github.com/k8sgpt-ai/k8sgpt/commit/81da402d46e1a1db83a41b717dfb23eb07d2e919))\n* **deps:** update golangci/golangci-lint-action digest to 9fae48a ([#1489](https://github.com/k8sgpt-ai/k8sgpt/issues/1489)) ([d5341f3](https://github.com/k8sgpt-ai/k8sgpt/commit/d5341f3c0019c1114254ac05f00c743a0354ec0b))\n\n## [0.4.15](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.14...v0.4.15) (2025-04-29)\n\n\n### Features\n\n* added token for goreleaser ([#1476](https://github.com/k8sgpt-ai/k8sgpt/issues/1476)) ([85935a4](https://github.com/k8sgpt-ai/k8sgpt/commit/85935a46d8f137b0339435cf19ce7f83ead97f8c))\n\n## [0.4.14](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.13...v0.4.14) (2025-04-29)\n\n\n### Features\n\n* add MCP support ([#1471](https://github.com/k8sgpt-ai/k8sgpt/issues/1471)) ([e41ffd8](https://github.com/k8sgpt-ai/k8sgpt/commit/e41ffd80d01ce7ae1fac9ce7e07344020d8bf914))\n* using modelName will calling completion ([#1469](https://github.com/k8sgpt-ai/k8sgpt/issues/1469)) ([f603948](https://github.com/k8sgpt-ai/k8sgpt/commit/f603948935f1c4cb171378634714577205de7b08))\n\n## [0.4.13](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.12...v0.4.13) (2025-04-22)\n\n\n### Features\n\n* slack announce ([#1466](https://github.com/k8sgpt-ai/k8sgpt/issues/1466)) ([3b6ad06](https://github.com/k8sgpt-ai/k8sgpt/commit/3b6ad06de1121c870fb486e0fe2bd1f87be16627))\n\n\n### Bug Fixes\n\n* reverse hpa ScalingLimited error condition ([#1366](https://github.com/k8sgpt-ai/k8sgpt/issues/1366)) ([ebb0373](https://github.com/k8sgpt-ai/k8sgpt/commit/ebb0373f69ad64a6cc43d0695d07e1d076c6366e))\n\n\n### Other\n\n* **deps:** update softprops/action-gh-release digest to da05d55 ([#1464](https://github.com/k8sgpt-ai/k8sgpt/issues/1464)) ([4434699](https://github.com/k8sgpt-ai/k8sgpt/commit/443469960a6b6791e358ee0a97e4c1dc5c3018e6))\n\n## [0.4.12](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.11...v0.4.12) (2025-04-17)\n\n\n### Features\n\n* new analyzers ([#1459](https://github.com/k8sgpt-ai/k8sgpt/issues/1459)) ([a128906](https://github.com/k8sgpt-ai/k8sgpt/commit/a128906136431189812d4d2dea68ea98cbfe5eeb))\n\n\n### Bug Fixes\n\n* **deps:** update module golang.org/x/net to v0.38.0 [security] ([#1462](https://github.com/k8sgpt-ai/k8sgpt/issues/1462)) ([e588fc3](https://github.com/k8sgpt-ai/k8sgpt/commit/e588fc316d29a29a7dde6abe2302833b38f1d302))\n\n\n### Other\n\n* **deps:** update codecov/codecov-action digest to ad3126e ([#1456](https://github.com/k8sgpt-ai/k8sgpt/issues/1456)) ([0553b98](https://github.com/k8sgpt-ai/k8sgpt/commit/0553b984b7c87b345f171bf6e5d632d890db689c))\n\n## [0.4.11](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.10...v0.4.11) (2025-04-15)\n\n\n### Features\n\n* add verbose flag to enable detailed output ([#1420](https://github.com/k8sgpt-ai/k8sgpt/issues/1420)) ([a79224e](https://github.com/k8sgpt-ai/k8sgpt/commit/a79224e2bf96f458dbc96404c8f4847970e8d2ef))\n* call bedrock with inference profile ([#1449](https://github.com/k8sgpt-ai/k8sgpt/issues/1449)) ([91d423b](https://github.com/k8sgpt-ai/k8sgpt/commit/91d423b147ca18cda7d54ff19349938a894ecb85))\n* improved test coverage ([#1455](https://github.com/k8sgpt-ai/k8sgpt/issues/1455)) ([80904e3](https://github.com/k8sgpt-ai/k8sgpt/commit/80904e3063b00b0536171b7b62b938938b20825a))\n\n\n### Bug Fixes\n\n* config ai provider in query ([#1457](https://github.com/k8sgpt-ai/k8sgpt/issues/1457)) ([df17e3e](https://github.com/k8sgpt-ai/k8sgpt/commit/df17e3e728591e974703527dff86de882af17790))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1447](https://github.com/k8sgpt-ai/k8sgpt/issues/1447)) ([969fe99](https://github.com/k8sgpt-ai/k8sgpt/commit/969fe99b3320c313f1c97133cdffb668a00d5fb5))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1453](https://github.com/k8sgpt-ai/k8sgpt/issues/1453)) ([cf6f928](https://github.com/k8sgpt-ai/k8sgpt/commit/cf6f9289e13ee729c24968fd771c901f412e8db7))\n\n\n### Docs\n\n* fix the slack invite link ([#1450](https://github.com/k8sgpt-ai/k8sgpt/issues/1450)) ([9ce3346](https://github.com/k8sgpt-ai/k8sgpt/commit/9ce33469d85aa0829e995e4b404ae85734124fb4))\n\n## [0.4.10](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.9...v0.4.10) (2025-04-10)\n\n\n### Features\n\n* add a naive support of bedrock inference profile ([#1446](https://github.com/k8sgpt-ai/k8sgpt/issues/1446)) ([78ffa59](https://github.com/k8sgpt-ai/k8sgpt/commit/78ffa5904addf71caf04554966437b14351f21e5))\n\n\n### Bug Fixes\n\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1417](https://github.com/k8sgpt-ai/k8sgpt/issues/1417)) ([ce4b3c2](https://github.com/k8sgpt-ai/k8sgpt/commit/ce4b3c2e7d0762093506d9010eceb47a2dcdf5bc))\n* **deps:** update module helm.sh/helm/v3 to v3.17.3 [security] ([#1448](https://github.com/k8sgpt-ai/k8sgpt/issues/1448)) ([060a3b2](https://github.com/k8sgpt-ai/k8sgpt/commit/060a3b2a26f117827090697eb599cd51a44125e6))\n* pod analyzer catches errors when containers are in Terminated state ([#1438](https://github.com/k8sgpt-ai/k8sgpt/issues/1438)) ([dceda9a](https://github.com/k8sgpt-ai/k8sgpt/commit/dceda9a6a16a914b916c478ecd0b4c8ed0e19c40))\n\n## [0.4.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.8...v0.4.9) (2025-04-08)\n\n\n### Other\n\n* **deps:** pin dependencies ([#1440](https://github.com/k8sgpt-ai/k8sgpt/issues/1440)) ([a5574ee](https://github.com/k8sgpt-ai/k8sgpt/commit/a5574ee49d530960a515c419f4875cf02cb36fb3))\n* fixing ([#1437](https://github.com/k8sgpt-ai/k8sgpt/issues/1437)) ([f68ff0e](https://github.com/k8sgpt-ai/k8sgpt/commit/f68ff0efee9bad5f8368c83800611fa9acbc53d7))\n\n## [0.4.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.7...v0.4.8) (2025-04-07)\n\n\n### Other\n\n* removed krew release ([#1434](https://github.com/k8sgpt-ai/k8sgpt/issues/1434)) ([39ae2aa](https://github.com/k8sgpt-ai/k8sgpt/commit/39ae2aa6351d6a77e0b45ad15b0d10b86a33f3be))\n\n## [0.4.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.6...v0.4.7) (2025-04-07)\n\n\n### Other\n\n* **deps:** update actions/upload-artifact digest to ea165f8 ([#1425](https://github.com/k8sgpt-ai/k8sgpt/issues/1425)) ([9bffc7c](https://github.com/k8sgpt-ai/k8sgpt/commit/9bffc7cff776733f6d05669e6c02f594ee2db261))\n* fixing build ([#1431](https://github.com/k8sgpt-ai/k8sgpt/issues/1431)) ([c5fe2c6](https://github.com/k8sgpt-ai/k8sgpt/commit/c5fe2c68d18d4fd713b3e638066327ad586d1871))\n\n## [0.4.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.5...v0.4.6) (2025-04-07)\n\n\n### Other\n\n* **deps:** pin docker/build-push-action action to 471d1dc ([#1428](https://github.com/k8sgpt-ai/k8sgpt/issues/1428)) ([5086ccd](https://github.com/k8sgpt-ai/k8sgpt/commit/5086ccd65942ebb9a37bd2c3a48d16c4be99e8c1))\n* fixing docker build push action ([#1426](https://github.com/k8sgpt-ai/k8sgpt/issues/1426)) ([1681aad](https://github.com/k8sgpt-ai/k8sgpt/commit/1681aadac106c608de9774ebfd7ea9df20eed482))\n* updated actor for login ([#1430](https://github.com/k8sgpt-ai/k8sgpt/issues/1430)) ([b626102](https://github.com/k8sgpt-ai/k8sgpt/commit/b6261026f8b41e505359a52c18bebec7ef5079f9))\n\n## [0.4.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.4...v0.4.5) (2025-04-07)\n\n\n### Other\n\n* fix workflows ([#1423](https://github.com/k8sgpt-ai/k8sgpt/issues/1423)) ([3dbc9e1](https://github.com/k8sgpt-ai/k8sgpt/commit/3dbc9e1a20a3a55971733d990ecd39e798a804e9))\n\n## [0.4.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.3...v0.4.4) (2025-04-06)\n\n\n### Other\n\n* **deps:** update docker/setup-buildx-action digest to b5ca514 ([#1371](https://github.com/k8sgpt-ai/k8sgpt/issues/1371)) ([d4de5d9](https://github.com/k8sgpt-ai/k8sgpt/commit/d4de5d9e3fdd1cc4c7d6fc067a7426fef1d32c1d))\n* **deps:** update module github.com/docker/docker to v28 ([#1376](https://github.com/k8sgpt-ai/k8sgpt/issues/1376)) ([68ddac0](https://github.com/k8sgpt-ai/k8sgpt/commit/68ddac008955933ffa27c2c4e46d286d9a26e100))\n* updating deps ([#1422](https://github.com/k8sgpt-ai/k8sgpt/issues/1422)) ([5b7fb7e](https://github.com/k8sgpt-ai/k8sgpt/commit/5b7fb7e6199635e109c1bf7355bc11ff6f60071b))\n\n## [0.4.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.2...v0.4.3) (2025-04-04)\n\n\n### Bug Fixes\n\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1363](https://github.com/k8sgpt-ai/k8sgpt/issues/1363)) ([e4861e9](https://github.com/k8sgpt-ai/k8sgpt/commit/e4861e9e2d631652b82768567afb9ba174114134))\n* prometheus UTF8Validation ([#1404](https://github.com/k8sgpt-ai/k8sgpt/issues/1404)) ([3c353b0](https://github.com/k8sgpt-ai/k8sgpt/commit/3c353b0e931028f3be3b229518cf86d24422a29d))\n\n\n### Other\n\n* added new AmazonBedrock model  ([#1390](https://github.com/k8sgpt-ai/k8sgpt/issues/1390)) ([ad2c90a](https://github.com/k8sgpt-ai/k8sgpt/commit/ad2c90a129074a13dac4fdd8e918d8e26159c7a1))\n* **deps:** pin golangci/golangci-lint-action action to 1481404 ([#1415](https://github.com/k8sgpt-ai/k8sgpt/issues/1415)) ([e231032](https://github.com/k8sgpt-ai/k8sgpt/commit/e231032e1bec1d2d25cb03b35e701aa86a61d5ee))\n* **deps:** update goreleaser/goreleaser-action digest to 9c156ee ([#1411](https://github.com/k8sgpt-ai/k8sgpt/issues/1411)) ([c823de1](https://github.com/k8sgpt-ai/k8sgpt/commit/c823de12e6b6efcf9f5639665aac602ed85ae31d))\n* linter ([#1414](https://github.com/k8sgpt-ai/k8sgpt/issues/1414)) ([f0b18cf](https://github.com/k8sgpt-ai/k8sgpt/commit/f0b18cfb1cd418b94b448d3b9de43f03841c92bb))\n\n\n### Docs\n\n* add table of contents and cleanup ([#1413](https://github.com/k8sgpt-ai/k8sgpt/issues/1413)) ([a31d07c](https://github.com/k8sgpt-ai/k8sgpt/commit/a31d07c802694d3455b665382ff12a2abc3e0ef7))\n* remove extra dollar sign in README.md ([#1410](https://github.com/k8sgpt-ai/k8sgpt/issues/1410)) ([a962741](https://github.com/k8sgpt-ai/k8sgpt/commit/a962741220bf98e159f14895d01cd596a7691f87))\n\n## [0.4.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.1...v0.4.2) (2025-03-28)\n\n\n### Features\n\n* old sonnet ([#1408](https://github.com/k8sgpt-ai/k8sgpt/issues/1408)) ([e5817f9](https://github.com/k8sgpt-ai/k8sgpt/commit/e5817f9e557f4f97b016a0a7b7674342c3a1773e))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 1f6e0b7 ([#1405](https://github.com/k8sgpt-ai/k8sgpt/issues/1405)) ([f5eaf81](https://github.com/k8sgpt-ai/k8sgpt/commit/f5eaf817f0cf2b732013e67e94c758a225c35ba6))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to 0aaccfd ([#1401](https://github.com/k8sgpt-ai/k8sgpt/issues/1401)) ([81d4aaf](https://github.com/k8sgpt-ai/k8sgpt/commit/81d4aaf402647bf4bcbc618fd82f9518cf3a5b4d))\n* **deps:** update actions/upload-artifact digest to ea165f8 ([#1402](https://github.com/k8sgpt-ai/k8sgpt/issues/1402)) ([eb381b8](https://github.com/k8sgpt-ai/k8sgpt/commit/eb381b8087bbb3216d9bcdcc88a71fbad9e31e41))\n* **deps:** update docker/login-action digest to 74a5d14 ([#1397](https://github.com/k8sgpt-ai/k8sgpt/issues/1397)) ([fdf8e7a](https://github.com/k8sgpt-ai/k8sgpt/commit/fdf8e7a95a6667b782e1e347a3b1d2fb0f2aafde))\n* fix error ([#1403](https://github.com/k8sgpt-ai/k8sgpt/issues/1403)) ([288ca86](https://github.com/k8sgpt-ai/k8sgpt/commit/288ca862b3aaf942e58aa0dad0e15e2fda84780f))\n\n## [0.4.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.0...v0.4.1) (2025-03-17)\n\n\n### Features\n\n* add amazon bedrock nova pro and nova lite models ([#1383](https://github.com/k8sgpt-ai/k8sgpt/issues/1383)) ([aa1e237](https://github.com/k8sgpt-ai/k8sgpt/commit/aa1e237ebb8c816383561c9b3e6a1ca0ddea8f78))\n* add custom restful backend for complex scenarios (e.g, rag) ([#1228](https://github.com/k8sgpt-ai/k8sgpt/issues/1228)) ([7540e00](https://github.com/k8sgpt-ai/k8sgpt/commit/7540e0084e0c0c44fc52ed9a906b76f9f2e6a981))\n\n\n### Bug Fixes\n\n* **deps:** update default model to gpt-4o for improved performance and cost efficiency ([#1332](https://github.com/k8sgpt-ai/k8sgpt/issues/1332)) ([4e39cb6](https://github.com/k8sgpt-ai/k8sgpt/commit/4e39cb65b3a7fc0d1c057c647794346e072d3fd0))\n* **deps:** update module golang.org/x/net to v0.36.0 [security] ([#1395](https://github.com/k8sgpt-ai/k8sgpt/issues/1395)) ([eb7b36a](https://github.com/k8sgpt-ai/k8sgpt/commit/eb7b36aa2764bc460ffc29a0aee18abe3631c2ed))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to f111f33 ([#1364](https://github.com/k8sgpt-ai/k8sgpt/issues/1364)) ([f2fdfd8](https://github.com/k8sgpt-ai/k8sgpt/commit/f2fdfd8dcaae6f57378d50396c4746d738d38bf2))\n* **deps:** update goreleaser/goreleaser-action digest to 90a3faa ([#1308](https://github.com/k8sgpt-ai/k8sgpt/issues/1308)) ([d6d2e3b](https://github.com/k8sgpt-ai/k8sgpt/commit/d6d2e3bc4254877c8af61aba7386706e942e3fe9))\n* **deps:** update softprops/action-gh-release digest to c95fe14 ([#1359](https://github.com/k8sgpt-ai/k8sgpt/issues/1359)) ([db5e517](https://github.com/k8sgpt-ai/k8sgpt/commit/db5e517dbb23a4cb0f203427744f4007d6e9faa8))\n\n## [0.4.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.50...v0.4.0) (2025-03-06)\n\n\n### ⚠ BREAKING CHANGES\n\n* Removal of Trivy ([#1386](https://github.com/k8sgpt-ai/k8sgpt/issues/1386))\n\n### Features\n\n* Removal of Trivy ([#1386](https://github.com/k8sgpt-ai/k8sgpt/issues/1386)) ([d1b2227](https://github.com/k8sgpt-ai/k8sgpt/commit/d1b2227ff9a8ef42bf63c83e289fbd801706821e))\n\n\n### Bug Fixes\n\n* [Bug] Filter PolicyReport ignores namespace flag ([#1355](https://github.com/k8sgpt-ai/k8sgpt/issues/1355)) ([9dcb21e](https://github.com/k8sgpt-ai/k8sgpt/commit/9dcb21e160233eb120ccf50f9b9b80c145d0e01a))\n\n\n### Other\n\n* Adding region ([#1388](https://github.com/k8sgpt-ai/k8sgpt/issues/1388)) ([4f4f4f1](https://github.com/k8sgpt-ai/k8sgpt/commit/4f4f4f13a065ca7add283088c93777f78dcea228))\n* **deps:** update actions/upload-artifact digest to 4cec3d8 ([#1378](https://github.com/k8sgpt-ai/k8sgpt/issues/1378)) ([093975e](https://github.com/k8sgpt-ai/k8sgpt/commit/093975e50ddadeab70a7c4f544df8351ac9758a2))\n* **deps:** update codecov/codecov-action digest to 0565863 ([#1387](https://github.com/k8sgpt-ai/k8sgpt/issues/1387)) ([2a6f485](https://github.com/k8sgpt-ai/k8sgpt/commit/2a6f48500c4567519453fc51ea070f5e407d3cfb))\n* **deps:** update docker/build-push-action digest to 471d1dc ([#1358](https://github.com/k8sgpt-ai/k8sgpt/issues/1358)) ([f2e3b9a](https://github.com/k8sgpt-ai/k8sgpt/commit/f2e3b9a8a72c4df32713197e50756e37e1302ff9))\n* remediating security issue ([#1381](https://github.com/k8sgpt-ai/k8sgpt/issues/1381)) ([1f95358](https://github.com/k8sgpt-ai/k8sgpt/commit/1f953585c91f8a208db3b37440e4d458b8d821eb))\n\n## [0.3.50](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.49...v0.3.50) (2025-02-24)\n\n\n### Features\n\n* rework to how bedrock data models are structured and accessed ([#1369](https://github.com/k8sgpt-ai/k8sgpt/issues/1369)) ([7dadea2](https://github.com/k8sgpt-ai/k8sgpt/commit/7dadea257007df64148f1e47f7960d1d30df67b2))\n\n## [0.3.49](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.48...v0.3.49) (2025-02-20)\n\n\n### Bug Fixes\n\n* **deps:** update all non-major dependencies ([#1335](https://github.com/k8sgpt-ai/k8sgpt/issues/1335)) ([8cd3b29](https://github.com/k8sgpt-ai/k8sgpt/commit/8cd3b2985e4cd61711497fb0436e72b6b8aa3162))\n* **deps:** update k8s.io/utils digest to 24370be ([#1344](https://github.com/k8sgpt-ai/k8sgpt/issues/1344)) ([fcc8563](https://github.com/k8sgpt-ai/k8sgpt/commit/fcc8563e4eba9bf45d49901b7287d311b93372c2))\n* **deps:** update module golang.org/x/net to v0.33.0 [security] ([#1354](https://github.com/k8sgpt-ai/k8sgpt/issues/1354)) ([5de4f77](https://github.com/k8sgpt-ai/k8sgpt/commit/5de4f7704a856fd7db7b2f800bda40c5beb9333b))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1336](https://github.com/k8sgpt-ai/k8sgpt/issues/1336)) ([19abbef](https://github.com/k8sgpt-ai/k8sgpt/commit/19abbef9a3112ceb060ac3fd772e2e4f62f19f84))\n* prevent npe by handling checking error in NewAnalysis call ([#1365](https://github.com/k8sgpt-ai/k8sgpt/issues/1365)) ([83672fa](https://github.com/k8sgpt-ai/k8sgpt/commit/83672fa768887dd1c6f4dc12a92c3444f100c4f6))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to 3041bf5 ([#1347](https://github.com/k8sgpt-ai/k8sgpt/issues/1347)) ([939e067](https://github.com/k8sgpt-ai/k8sgpt/commit/939e0672aaaa5538cd58bb171f1e5d1c07831651))\n* **deps:** update actions/upload-artifact digest to 65c4c4a ([#1350](https://github.com/k8sgpt-ai/k8sgpt/issues/1350)) ([c506a4b](https://github.com/k8sgpt-ai/k8sgpt/commit/c506a4b441e24052398c00c93d96806cec1b9f75))\n* **deps:** update codecov/codecov-action digest to 13ce06b ([#1342](https://github.com/k8sgpt-ai/k8sgpt/issues/1342)) ([990d723](https://github.com/k8sgpt-ai/k8sgpt/commit/990d7239091b368178e06af60e4dc0e897fc8236))\n* **deps:** update docker/setup-buildx-action digest to 6524bf6 ([#1349](https://github.com/k8sgpt-ai/k8sgpt/issues/1349)) ([2918556](https://github.com/k8sgpt-ai/k8sgpt/commit/2918556793316ea4f5a319c9aa51c1fec12ede85))\n* fix typo in \"completion\" ([#1362](https://github.com/k8sgpt-ai/k8sgpt/issues/1362)) ([06b8f78](https://github.com/k8sgpt-ai/k8sgpt/commit/06b8f78150308c1f6023747fa34826e038d6bc3a))\n\n\n### Docs\n\n* fix broken schema link in README.md ([#1373](https://github.com/k8sgpt-ai/k8sgpt/issues/1373)) ([076ca2f](https://github.com/k8sgpt-ai/k8sgpt/commit/076ca2f14832cf83e43c465c377ef21825218b2f))\n\n## [0.3.48](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.47...v0.3.48) (2024-12-04)\n\n\n### Features\n\n* fixed missing cache params ([#1340](https://github.com/k8sgpt-ai/k8sgpt/issues/1340)) ([1363219](https://github.com/k8sgpt-ai/k8sgpt/commit/1363219b1b94e157ef03c53eba8838b7cef559b4))\n\n## [0.3.47](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.46...v0.3.47) (2024-12-02)\n\n\n### Features\n\n* add new AWS Bedrock model ids ([#1330](https://github.com/k8sgpt-ai/k8sgpt/issues/1330)) ([a12aa07](https://github.com/k8sgpt-ai/k8sgpt/commit/a12aa07b1a2e34c5106b7b930b29b0c97b172dc4))\n* adds interplex as a caching provider ([#1328](https://github.com/k8sgpt-ai/k8sgpt/issues/1328)) ([d6d80ee](https://github.com/k8sgpt-ai/k8sgpt/commit/d6d80ee86083643d9b91457791bfc77ef475e82e))\n* dump ([#1322](https://github.com/k8sgpt-ai/k8sgpt/issues/1322)) ([da266b3](https://github.com/k8sgpt-ai/k8sgpt/commit/da266b3c82ca8b3e96461be688a9f30e408568fe))\n\n\n### Bug Fixes\n\n* add maxTokens to serve mode ([#1280](https://github.com/k8sgpt-ai/k8sgpt/issues/1280)) ([a50375c](https://github.com/k8sgpt-ai/k8sgpt/commit/a50375c9605a87546a0fcbcacabe5482fdfa1c2c))\n* **deps:** update all non-major dependencies ([#1323](https://github.com/k8sgpt-ai/k8sgpt/issues/1323)) ([b3f60b2](https://github.com/k8sgpt-ai/k8sgpt/commit/b3f60b2d2018d4bede3918adcb3547ef2acf6688))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1303](https://github.com/k8sgpt-ai/k8sgpt/issues/1303)) ([2da0573](https://github.com/k8sgpt-ai/k8sgpt/commit/2da057360b378d34126e1480ade0686f104e3ace))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1321](https://github.com/k8sgpt-ai/k8sgpt/issues/1321)) ([69c67bd](https://github.com/k8sgpt-ai/k8sgpt/commit/69c67bd1d9d4404816a8b7a00c98499729f2185f))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1326](https://github.com/k8sgpt-ai/k8sgpt/issues/1326)) ([5514ebb](https://github.com/k8sgpt-ai/k8sgpt/commit/5514ebb53b79b5bac0fc861ffdebc9399fe87b62))\n* update OpenAI API key generation URL to reflect new platform link ([#1331](https://github.com/k8sgpt-ai/k8sgpt/issues/1331)) ([ec5e42b](https://github.com/k8sgpt-ai/k8sgpt/commit/ec5e42b8f43e90632bb62dd89cc6aa3665e0f60d))\n\n\n### Other\n\n* **deps:** update all non-major dependencies ([#1327](https://github.com/k8sgpt-ai/k8sgpt/issues/1327)) ([a841568](https://github.com/k8sgpt-ai/k8sgpt/commit/a841568a9c4c0012291cf8f4248250192b72a383))\n* **deps:** update codecov/codecov-action action to v5 ([#1324](https://github.com/k8sgpt-ai/k8sgpt/issues/1324)) ([cb1e1ff](https://github.com/k8sgpt-ai/k8sgpt/commit/cb1e1ffede1d3086d54157142c6803341e560ca8))\n* **deps:** update codecov/codecov-action digest to 015f24e ([#1325](https://github.com/k8sgpt-ai/k8sgpt/issues/1325)) ([4d7eb0f](https://github.com/k8sgpt-ai/k8sgpt/commit/4d7eb0f6226fc50f58b5c2fff7534dd16e2ca378))\n* **deps:** update docker/build-push-action action to v6 ([#1294](https://github.com/k8sgpt-ai/k8sgpt/issues/1294)) ([f37d923](https://github.com/k8sgpt-ai/k8sgpt/commit/f37d92391877819c6d26a993ab58bc0c49fb3b66))\n* **deps:** update docker/build-push-action digest to 48aba3b ([#1333](https://github.com/k8sgpt-ai/k8sgpt/issues/1333)) ([c21ba86](https://github.com/k8sgpt-ai/k8sgpt/commit/c21ba86237db651086c0a37abc3454db513e505b))\n* **deps:** update rajatjindal/krew-release-bot action to v0.0.47 ([#1317](https://github.com/k8sgpt-ai/k8sgpt/issues/1317)) ([896a53b](https://github.com/k8sgpt-ai/k8sgpt/commit/896a53be8394c490e2d34f151de44c3663dddf5b))\n\n## [0.3.46](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.45...v0.3.46) (2024-11-10)\n\n\n### Features\n\n* reverting the cncf runners ([#1319](https://github.com/k8sgpt-ai/k8sgpt/issues/1319)) ([ad86e7a](https://github.com/k8sgpt-ai/k8sgpt/commit/ad86e7aa39995c492437627dbd9f89f152f11f2c))\n* switching to higher spec runners ([#1312](https://github.com/k8sgpt-ai/k8sgpt/issues/1312)) ([5f7d9de](https://github.com/k8sgpt-ai/k8sgpt/commit/5f7d9de46a521463cedc901b729fe27f8d86f381))\n* testupdate ([#1315](https://github.com/k8sgpt-ai/k8sgpt/issues/1315)) ([7dcdfc8](https://github.com/k8sgpt-ai/k8sgpt/commit/7dcdfc83d2461e4342ded5fa80493936b70f64a1))\n* updated runners to enterprise ([#1318](https://github.com/k8sgpt-ai/k8sgpt/issues/1318)) ([1ae70e8](https://github.com/k8sgpt-ai/k8sgpt/commit/1ae70e806e2609c8fb964f0a577304d07b365cae))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to 41dfa10 ([#1284](https://github.com/k8sgpt-ai/k8sgpt/issues/1284)) ([2ce8450](https://github.com/k8sgpt-ai/k8sgpt/commit/2ce8450e03986904a7ffe7afac4b5ba777c67c57))\n* **deps:** update softprops/action-gh-release action to v2 ([#1295](https://github.com/k8sgpt-ai/k8sgpt/issues/1295)) ([b6b3d0c](https://github.com/k8sgpt-ai/k8sgpt/commit/b6b3d0c8566b0dbd9cb0e5f59c8493e4343e0106))\n\n## [0.3.45](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.44...v0.3.45) (2024-11-10)\n\n\n### Features\n\n* free disk ([#1313](https://github.com/k8sgpt-ai/k8sgpt/issues/1313)) ([783cd1c](https://github.com/k8sgpt-ai/k8sgpt/commit/783cd1cfc66f8e4489e5006529745d8caf38cfd4))\n\n## [0.3.44](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.43...v0.3.44) (2024-11-09)\n\n\n### Features\n\n* test revert runner on release job ([#1310](https://github.com/k8sgpt-ai/k8sgpt/issues/1310)) ([cc9b3ea](https://github.com/k8sgpt-ai/k8sgpt/commit/cc9b3ea6579c6190629e0fac48e37e0eba650158))\n\n## [0.3.43](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.42...v0.3.43) (2024-11-05)\n\n\n### Bug Fixes\n\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1296](https://github.com/k8sgpt-ai/k8sgpt/issues/1296)) ([2f75986](https://github.com/k8sgpt-ai/k8sgpt/commit/2f759865b6fc5ae143c8f5e89a306abc89d4de27))\n\n\n### Other\n\n* **deps:** update dependency ubuntu to v24 ([#1293](https://github.com/k8sgpt-ai/k8sgpt/issues/1293)) ([c67add3](https://github.com/k8sgpt-ai/k8sgpt/commit/c67add30c64257ac6258dec93193e3201ba8c4ab))\n\n## [0.3.42](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.41...v0.3.42) (2024-11-04)\n\n\n### Features\n\n* add stats option to analyze command for performance insights ([#1237](https://github.com/k8sgpt-ai/k8sgpt/issues/1237)) ([3eec9bb](https://github.com/k8sgpt-ai/k8sgpt/commit/3eec9bbb05b2f0717437cc4a2ec786594ece1cc3))\n* error from events for STS analyzer ([#1256](https://github.com/k8sgpt-ai/k8sgpt/issues/1256)) ([d8fad95](https://github.com/k8sgpt-ai/k8sgpt/commit/d8fad956f45a4dd668647379bb0295e169faeac6))\n\n\n### Bug Fixes\n\n* [Bug] Make lint command is not working ([#1282](https://github.com/k8sgpt-ai/k8sgpt/issues/1282)) ([87565a0](https://github.com/k8sgpt-ai/k8sgpt/commit/87565a0bcce7087114798c3a32877894c8a9dcee))\n* add providerId to serve mode ([#1260](https://github.com/k8sgpt-ai/k8sgpt/issues/1260)) ([da0764d](https://github.com/k8sgpt-ai/k8sgpt/commit/da0764d951ca76cb7007c412f8efa794619c20ba))\n* **deps:** update all non-major dependencies ([#1291](https://github.com/k8sgpt-ai/k8sgpt/issues/1291)) ([14e0f19](https://github.com/k8sgpt-ai/k8sgpt/commit/14e0f19b12189052b03d551e409b407fd0b6bd30))\n* **deps:** update k8s.io/utils digest to 49e7df5 ([#1259](https://github.com/k8sgpt-ai/k8sgpt/issues/1259)) ([7785dd1](https://github.com/k8sgpt-ai/k8sgpt/commit/7785dd12a0245a33af25dedd2fbb5f4178b5cda9))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go to v1.35.1-20240920204244-7a91c8620515.1 ([#1274](https://github.com/k8sgpt-ai/k8sgpt/issues/1274)) ([9f39abf](https://github.com/k8sgpt-ai/k8sgpt/commit/9f39abf89e4e92009f5e138d9b01d11c60ac135c))\n* **deps:** update module cloud.google.com/go/storage to v1.44.0 ([#1265](https://github.com/k8sgpt-ai/k8sgpt/issues/1265)) ([4143e9f](https://github.com/k8sgpt-ai/k8sgpt/commit/4143e9fd524bed3179524d949b7b0f92c02ecd11))\n* **deps:** update module github.com/adrg/xdg to v0.5.0 ([#1262](https://github.com/k8sgpt-ai/k8sgpt/issues/1262)) ([98237b6](https://github.com/k8sgpt-ai/k8sgpt/commit/98237b6408521ee7afc05fcaed2f78ba79e77144))\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.22.0 ([#1034](https://github.com/k8sgpt-ai/k8sgpt/issues/1034)) ([037e745](https://github.com/k8sgpt-ai/k8sgpt/commit/037e745c6f667830f0e1d531ce4bbd07083ef972))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.55.5 ([#1263](https://github.com/k8sgpt-ai/k8sgpt/issues/1263)) ([0148a5b](https://github.com/k8sgpt-ai/k8sgpt/commit/0148a5b3549cbdb6c6e5832dc01aab044b90ddc9))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.8.0 ([#1264](https://github.com/k8sgpt-ai/k8sgpt/issues/1264)) ([3613585](https://github.com/k8sgpt-ai/k8sgpt/commit/36135857ac55e126b3a6c4533a000cb0b7f32c6b))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.4.1 ([#1275](https://github.com/k8sgpt-ai/k8sgpt/issues/1275)) ([c9b11b6](https://github.com/k8sgpt-ai/k8sgpt/commit/c9b11b6eee00d0269a4d48ad2e4be5458436b51d))\n* **deps:** update module github.com/cohere-ai/cohere-go/v2 to v2.12.0 ([#1276](https://github.com/k8sgpt-ai/k8sgpt/issues/1276)) ([7a3fb3c](https://github.com/k8sgpt-ai/k8sgpt/commit/7a3fb3cf6777d5b0babf00455c3833a47bb1bfdb))\n* **deps:** update module github.com/google/generative-ai-go to v0.18.0 ([#1278](https://github.com/k8sgpt-ai/k8sgpt/issues/1278)) ([ad349ae](https://github.com/k8sgpt-ai/k8sgpt/commit/ad349ae263f226e300f60dd092729c5a3bf61dbe))\n* rename watsonxai to ibmwatsonxai ([#1234](https://github.com/k8sgpt-ai/k8sgpt/issues/1234)) ([5ff6dc9](https://github.com/k8sgpt-ai/k8sgpt/commit/5ff6dc9be5218e47839c4ac5e8f3458b40eb9c88))\n\n\n### Other\n\n* **deps:** update actions/checkout digest to 11bd719 ([#1283](https://github.com/k8sgpt-ai/k8sgpt/issues/1283)) ([0cfecbd](https://github.com/k8sgpt-ai/k8sgpt/commit/0cfecbdd87586fd138cc63c4e7a26d54e7ed83a8))\n* **deps:** update actions/checkout digest to eef6144 ([#1270](https://github.com/k8sgpt-ai/k8sgpt/issues/1270)) ([72eb815](https://github.com/k8sgpt-ai/k8sgpt/commit/72eb8159fb4a2284cf43eb6a5f3de7bed10c6224))\n* **deps:** update actions/upload-artifact digest to b4b15b8 ([#1272](https://github.com/k8sgpt-ai/k8sgpt/issues/1272)) ([911d578](https://github.com/k8sgpt-ai/k8sgpt/commit/911d578bf006253d10fe21d96888ddf34a8b4691))\n* **deps:** update anchore/sbom-action action to v0.17.2 ([#1248](https://github.com/k8sgpt-ai/k8sgpt/issues/1248)) ([04582d8](https://github.com/k8sgpt-ai/k8sgpt/commit/04582d85160055da30e4e00fd3c6ca69d1decd1a))\n* **deps:** update anchore/sbom-action action to v0.17.4 ([#1273](https://github.com/k8sgpt-ai/k8sgpt/issues/1273)) ([c128bf7](https://github.com/k8sgpt-ai/k8sgpt/commit/c128bf7942e380fcab5e9771f405471198e388fe))\n* **deps:** update anchore/sbom-action action to v0.17.6 ([#1285](https://github.com/k8sgpt-ai/k8sgpt/issues/1285)) ([173e4dc](https://github.com/k8sgpt-ai/k8sgpt/commit/173e4dc5ac6265af4a3538556220d3a43ab721f7))\n* **deps:** update codecov/codecov-action action to v4 ([#1292](https://github.com/k8sgpt-ai/k8sgpt/issues/1292)) ([c1a38c2](https://github.com/k8sgpt-ai/k8sgpt/commit/c1a38c2b35a0bfa772b88f15843c9354b0345284))\n* **deps:** update docker/setup-buildx-action digest to c47758b ([#1213](https://github.com/k8sgpt-ai/k8sgpt/issues/1213)) ([161bc11](https://github.com/k8sgpt-ai/k8sgpt/commit/161bc11294d5094533068cf7af9880795a61536e))\n* **deps:** update golang docker tag to v1.23 ([#1254](https://github.com/k8sgpt-ai/k8sgpt/issues/1254)) ([b62b7db](https://github.com/k8sgpt-ai/k8sgpt/commit/b62b7dbe3c9cd02b81f6a0111bca939034c5cc9f))\n* **deps:** update module github.com/docker/docker to v27.3.1+incompatible ([#1225](https://github.com/k8sgpt-ai/k8sgpt/issues/1225)) ([9c1927b](https://github.com/k8sgpt-ai/k8sgpt/commit/9c1927b4975fa8132fbc24dd96a5737819855544))\n* renovate.json ([#1290](https://github.com/k8sgpt-ai/k8sgpt/issues/1290)) ([458fcfe](https://github.com/k8sgpt-ai/k8sgpt/commit/458fcfe8d330523781d32af680febc2a0c0525a2))\n\n## [0.3.41](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.40...v0.3.41) (2024-09-22)\n\n\n### Features\n\n* add custom-analyzer cmd ([#1207](https://github.com/k8sgpt-ai/k8sgpt/issues/1207)) ([db26d24](https://github.com/k8sgpt-ai/k8sgpt/commit/db26d24ac607534ce78c1c82f3e1d4e5dde17578))\n* add event failure handling in service analyzer ([#1132](https://github.com/k8sgpt-ai/k8sgpt/issues/1132)) ([a4e44d5](https://github.com/k8sgpt-ai/k8sgpt/commit/a4e44d59e3ee63714cfd144228299e4f24ac3691))\n* added support for A21 and Amazon Titan models via bedrock api ([#1101](https://github.com/k8sgpt-ai/k8sgpt/issues/1101)) ([4f3ecf0](https://github.com/k8sgpt-ai/k8sgpt/commit/4f3ecf008351075068738e930ff3a657f597654a))\n* adding a query mode for the schednex scheduler ([#1257](https://github.com/k8sgpt-ai/k8sgpt/issues/1257)) ([53465d5](https://github.com/k8sgpt-ai/k8sgpt/commit/53465d5c832ac490403a2698b80122ca06372df7))\n* refactoring to the new schema ([#1219](https://github.com/k8sgpt-ai/k8sgpt/issues/1219)) ([02fa109](https://github.com/k8sgpt-ai/k8sgpt/commit/02fa109429d3c684079f5d488e7f517806fc1a09))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 702e33f ([#1246](https://github.com/k8sgpt-ai/k8sgpt/issues/1246)) ([d30563d](https://github.com/k8sgpt-ai/k8sgpt/commit/d30563d8cdedb5bbf48735e49ebcb44440a5f0f5))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 to v2.22.0-20240807134501-ea98c104104d.1 ([#1186](https://github.com/k8sgpt-ai/k8sgpt/issues/1186)) ([8405778](https://github.com/k8sgpt-ai/k8sgpt/commit/8405778cb25429d2b42d7a3b50ec88b45961a57f))\n* **deps:** update module github.com/docker/docker to v27.1.1+incompatible [security] ([#1220](https://github.com/k8sgpt-ai/k8sgpt/issues/1220)) ([3148b5c](https://github.com/k8sgpt-ai/k8sgpt/commit/3148b5c61d2ff57d67d966d6e915994d4aa8a844))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.12 ([#1226](https://github.com/k8sgpt-ai/k8sgpt/issues/1226)) ([7019d0b](https://github.com/k8sgpt-ai/k8sgpt/commit/7019d0b62f1bebbd4c2a251c98a2beb4975bf2fe))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.13 ([#1251](https://github.com/k8sgpt-ai/k8sgpt/issues/1251)) ([1dfd139](https://github.com/k8sgpt-ai/k8sgpt/commit/1dfd13973165bd2820aa8ca079e1ec656a5033f0))\n* **deps:** update module github.com/schollz/progressbar/v3 to v3.15.0 ([#1227](https://github.com/k8sgpt-ai/k8sgpt/issues/1227)) ([025a069](https://github.com/k8sgpt-ai/k8sgpt/commit/025a069ff1582131cede63420aa535a3b550b7b7))\n* disable adding multiple openai provider ([#1191](https://github.com/k8sgpt-ai/k8sgpt/issues/1191)) ([644581f](https://github.com/k8sgpt-ai/k8sgpt/commit/644581f4958f470cfb088a69a478db0ab91c1540))\n* enabled auth add support watsonx backend ([#1190](https://github.com/k8sgpt-ai/k8sgpt/issues/1190)) ([d702209](https://github.com/k8sgpt-ai/k8sgpt/commit/d702209941480dce62b9622ea30fdb4a9e5ef083))\n* helm chart security context rendering if empty ([#1235](https://github.com/k8sgpt-ai/k8sgpt/issues/1235)) ([be4ca86](https://github.com/k8sgpt-ai/k8sgpt/commit/be4ca86af07e832eb7832f7e5f83df8676bafd29))\n* issue-1168, remove duplicate CVE ([#1230](https://github.com/k8sgpt-ai/k8sgpt/issues/1230)) ([8edb053](https://github.com/k8sgpt-ai/k8sgpt/commit/8edb053b3e88027880a75999eab19bed2176747f))\n* segmentation violation during serve ([#1215](https://github.com/k8sgpt-ai/k8sgpt/issues/1215)) ([b7e5394](https://github.com/k8sgpt-ai/k8sgpt/commit/b7e5394caaabb43e01161618f7a6e9f4aa8f7408))\n* set logger for controller-runtime ([#1211](https://github.com/k8sgpt-ai/k8sgpt/issues/1211)) ([8e37369](https://github.com/k8sgpt-ai/k8sgpt/commit/8e37369e5c6c96096b66179f22a27b2c0018c43a))\n* typo ([#1244](https://github.com/k8sgpt-ai/k8sgpt/issues/1244)) ([e02c0dd](https://github.com/k8sgpt-ai/k8sgpt/commit/e02c0ddd2d9f9a6fae8a57514468f26fe72b567a))\n\n\n### Other\n\n* **deps:** update actions/checkout digest to 692973e ([#1129](https://github.com/k8sgpt-ai/k8sgpt/issues/1129)) ([24ebeaf](https://github.com/k8sgpt-ai/k8sgpt/commit/24ebeaf3a748f2bf40c18ddcecaf8655b457048b))\n* **deps:** update actions/upload-artifact digest to 5076954 ([#1239](https://github.com/k8sgpt-ai/k8sgpt/issues/1239)) ([e0e86ea](https://github.com/k8sgpt-ai/k8sgpt/commit/e0e86ea60f3811e8ee22fd9c28e91817c56104a2))\n* **deps:** update actions/upload-artifact digest to 834a144 ([#1214](https://github.com/k8sgpt-ai/k8sgpt/issues/1214)) ([2a8a9b4](https://github.com/k8sgpt-ai/k8sgpt/commit/2a8a9b486714d780c0df3ecae8757534249731dc))\n* **deps:** update anchore/sbom-action action to v0.17.1 ([#1224](https://github.com/k8sgpt-ai/k8sgpt/issues/1224)) ([f573819](https://github.com/k8sgpt-ai/k8sgpt/commit/f57381961fbc63305d9e9aa63e85a90a100ee553))\n* **deps:** update dependency go to v1.23.1 ([#1176](https://github.com/k8sgpt-ai/k8sgpt/issues/1176)) ([453d5c3](https://github.com/k8sgpt-ai/k8sgpt/commit/453d5c37ddafd93c6fa194b5b4fc0794154eb8c1))\n* **deps:** update docker/login-action digest to 9780b0c ([#1212](https://github.com/k8sgpt-ai/k8sgpt/issues/1212)) ([477ef15](https://github.com/k8sgpt-ai/k8sgpt/commit/477ef155d32f4d81ca3bee612644f51fc1098cdc))\n\n\n### Docs\n\n* update \"CLI Installation\" section in README.md ([#1126](https://github.com/k8sgpt-ai/k8sgpt/issues/1126)) ([#1127](https://github.com/k8sgpt-ai/k8sgpt/issues/1127)) ([b2b8682](https://github.com/k8sgpt-ai/k8sgpt/commit/b2b86826e55984c2b6aed6554869d7ce66a5f854))\n\n## [0.3.40](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.39...v0.3.40) (2024-08-04)\n\n\n### Features\n\n* custom analysis paralelism ([#1203](https://github.com/k8sgpt-ai/k8sgpt/issues/1203)) ([f1b7b37](https://github.com/k8sgpt-ai/k8sgpt/commit/f1b7b37fb83937d5fad90d7b6b52f4a38823da9e))\n* getting the error from status field for HPA analyzer ([#1164](https://github.com/k8sgpt-ai/k8sgpt/issues/1164)) ([a068310](https://github.com/k8sgpt-ai/k8sgpt/commit/a068310731d775beecede03a1709e541ffd68142))\n* initial custom analysis server mode ([#1205](https://github.com/k8sgpt-ai/k8sgpt/issues/1205)) ([16d57e5](https://github.com/k8sgpt-ai/k8sgpt/commit/16d57e5a55c2084bf1580377ae52e2961cc84922))\n\n\n### Bug Fixes\n\n* add default maxToken value of watsonxai backend ([#1209](https://github.com/k8sgpt-ai/k8sgpt/issues/1209)) ([d43fd87](https://github.com/k8sgpt-ai/k8sgpt/commit/d43fd878ba04fec8ac8afe4a1c15272b7f21c951))\n* auth update throw out exception ([#1193](https://github.com/k8sgpt-ai/k8sgpt/issues/1193)) ([391a3cd](https://github.com/k8sgpt-ai/k8sgpt/commit/391a3cd5adcbd90f37922332b4fad5ba5d813e5f))\n* **deps:** update module cloud.google.com/go/storage to v1.43.0 ([#1198](https://github.com/k8sgpt-ai/k8sgpt/issues/1198)) ([8949f5b](https://github.com/k8sgpt-ai/k8sgpt/commit/8949f5bac3c69130e30103511fdb5ece66e1619f))\n* **deps:** update module github.com/schollz/progressbar/v3 to v3.14.5 ([#1145](https://github.com/k8sgpt-ai/k8sgpt/issues/1145)) ([3547c48](https://github.com/k8sgpt-ai/k8sgpt/commit/3547c4808a846eb4392996afa20a84bdddf8e24f))\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.17.0 ([#1197](https://github.com/k8sgpt-ai/k8sgpt/issues/1197)) ([407c855](https://github.com/k8sgpt-ai/k8sgpt/commit/407c855e147b73739e800310c926826344d36323))\n\n## [0.3.39](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.38...v0.3.39) (2024-07-18)\n\n\n### Features\n\n* add label selector ([#1201](https://github.com/k8sgpt-ai/k8sgpt/issues/1201)) ([eb3b81f](https://github.com/k8sgpt-ai/k8sgpt/commit/eb3b81f1767c589474864992ae78001ab1b376a1))\n* fix the custom-analysis printing ([#1195](https://github.com/k8sgpt-ai/k8sgpt/issues/1195)) ([b6dd2a1](https://github.com/k8sgpt-ai/k8sgpt/commit/b6dd2a1181b478a4fb8543ab7529ce595fa7d4a8))\n* initial kyverno support ([#1200](https://github.com/k8sgpt-ai/k8sgpt/issues/1200)) ([5176759](https://github.com/k8sgpt-ai/k8sgpt/commit/5176759bd0fad8671164f9e75b31dec19f02bd54))\n* skip k3s node type EtcdIsVoter ([#1167](https://github.com/k8sgpt-ai/k8sgpt/issues/1167)) ([4366ad9](https://github.com/k8sgpt-ai/k8sgpt/commit/4366ad97b80d2df0400e06e4b892fadab3939dc7))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 18e509b ([#1183](https://github.com/k8sgpt-ai/k8sgpt/issues/1183)) ([0b90651](https://github.com/k8sgpt-ai/k8sgpt/commit/0b906511d5a9837c9a67cf819754c610b1becc5c))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.4.0-20240715142657-3785f0a44aae.2 ([#1196](https://github.com/k8sgpt-ai/k8sgpt/issues/1196)) ([f9edbf3](https://github.com/k8sgpt-ai/k8sgpt/commit/f9edbf34f3eb3e90528d04b1c470fd6ef15293ec))\n* **deps:** update module github.com/ibm/watsonx-go to v1.0.1 ([#1187](https://github.com/k8sgpt-ai/k8sgpt/issues/1187)) ([34b6de3](https://github.com/k8sgpt-ai/k8sgpt/commit/34b6de34041ce253c1c680a7f5fe535b03a50da5))\n* **deps:** update module github.com/prometheus/prometheus to v0.53.1 ([#1035](https://github.com/k8sgpt-ai/k8sgpt/issues/1035)) ([de9ef85](https://github.com/k8sgpt-ai/k8sgpt/commit/de9ef8587822814542661e0039b47ef65d902abb))\n\n\n### Other\n\n* **deps:** pin goreleaser/goreleaser-action action to 286f3b1 ([#1171](https://github.com/k8sgpt-ai/k8sgpt/issues/1171)) ([1a00aaf](https://github.com/k8sgpt-ai/k8sgpt/commit/1a00aafbb2f6f1482dfb3da7e96954b12ad5a4fd))\n* **deps:** update actions/setup-go digest to 0a12ed9 ([#1182](https://github.com/k8sgpt-ai/k8sgpt/issues/1182)) ([593139c](https://github.com/k8sgpt-ai/k8sgpt/commit/593139cffb1982fe45ccc9403acc893f51064271))\n* **deps:** update actions/upload-artifact digest to 0b2256b ([#1175](https://github.com/k8sgpt-ai/k8sgpt/issues/1175)) ([4b13727](https://github.com/k8sgpt-ai/k8sgpt/commit/4b13727ef579240adc2777d1126544fafb23b993))\n* **deps:** update anchore/sbom-action action to v0.16.1 ([#1179](https://github.com/k8sgpt-ai/k8sgpt/issues/1179)) ([3e93409](https://github.com/k8sgpt-ai/k8sgpt/commit/3e9340925c3d59861b1a95d5c1bc08c19ec26e4a))\n\n## [0.3.38](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.37...v0.3.38) (2024-07-10)\n\n\n### Features\n\n* add custom http headers to openai related api backends ([#1174](https://github.com/k8sgpt-ai/k8sgpt/issues/1174)) ([02e754e](https://github.com/k8sgpt-ai/k8sgpt/commit/02e754ed591742fccc5ff9a20c3e36e4475f6ec5))\n* add Ollama backend ([#1065](https://github.com/k8sgpt-ai/k8sgpt/issues/1065)) ([b35dbd9](https://github.com/k8sgpt-ai/k8sgpt/commit/b35dbd9b09197994f041cda04f1a4e5fb316e468))\n* add watsonx ai provider ([#1163](https://github.com/k8sgpt-ai/k8sgpt/issues/1163)) ([ce63821](https://github.com/k8sgpt-ai/k8sgpt/commit/ce63821bebbd87b2e058f5cf58a2cdd474b8fb58))\n\n\n### Bug Fixes\n\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 to v2.20.0-20240406062209-1cc152efbf5c.1 ([#1147](https://github.com/k8sgpt-ai/k8sgpt/issues/1147)) ([314f25a](https://github.com/k8sgpt-ai/k8sgpt/commit/314f25ac8bf5c3629474ece0eae6a3bda83099aa))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.10 ([#1177](https://github.com/k8sgpt-ai/k8sgpt/issues/1177)) ([fef8539](https://github.com/k8sgpt-ai/k8sgpt/commit/fef853966fc6e33dae0a9686fa767b36201c0228))\n* **deps:** update module github.com/spf13/cobra to v1.8.1 ([#1161](https://github.com/k8sgpt-ai/k8sgpt/issues/1161)) ([a075792](https://github.com/k8sgpt-ai/k8sgpt/commit/a0757921191205398539a6ccc8dbfaa503db595f))\n* **deps:** update module google.golang.org/grpc to v1.64.1 [security] ([#1178](https://github.com/k8sgpt-ai/k8sgpt/issues/1178)) ([dd20dbc](https://github.com/k8sgpt-ai/k8sgpt/commit/dd20dbc9829fc50f77ad6a32c3a10dcf221d2750))\n\n\n### Other\n\n* **deps:** update amannn/action-semantic-pull-request action to v5.5.3 ([#1172](https://github.com/k8sgpt-ai/k8sgpt/issues/1172)) ([27ac60a](https://github.com/k8sgpt-ai/k8sgpt/commit/27ac60aed296c3d9582f34e14c5985a4bccd991e))\n* **deps:** update anchore/sbom-action action to v0.16.0 ([#1146](https://github.com/k8sgpt-ai/k8sgpt/issues/1146)) ([dd66355](https://github.com/k8sgpt-ai/k8sgpt/commit/dd6635579789ce65ee86dc1196e7dfde1b7d20e6))\n* **deps:** update docker/build-push-action digest to ca052bb ([#1140](https://github.com/k8sgpt-ai/k8sgpt/issues/1140)) ([0c02160](https://github.com/k8sgpt-ai/k8sgpt/commit/0c0216096efde9c2c812ee90522c081f51c52631))\n* **deps:** update docker/setup-buildx-action digest to 4fd8129 ([#1173](https://github.com/k8sgpt-ai/k8sgpt/issues/1173)) ([d4abb33](https://github.com/k8sgpt-ai/k8sgpt/commit/d4abb33b3c29d9a2e4dee094ea7be2bc5d1807d1))\n* update brew installation note ([#1155](https://github.com/k8sgpt-ai/k8sgpt/issues/1155)) ([ab534d1](https://github.com/k8sgpt-ai/k8sgpt/commit/ab534d184fcd538f2ba10a6b5bf3a74c28d5fee6))\n\n## [0.3.37](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.36...v0.3.37) (2024-06-17)\n\n\n### Other\n\n* **deps:** update reviewdog/action-golangci-lint digest to 7708105 ([#1157](https://github.com/k8sgpt-ai/k8sgpt/issues/1157)) ([7b1b633](https://github.com/k8sgpt-ai/k8sgpt/commit/7b1b63322ec7b0c0864682bc23be6e70c0ed7ec7))\n* updated the goreleaser action ([#1160](https://github.com/k8sgpt-ai/k8sgpt/issues/1160)) ([9bace02](https://github.com/k8sgpt-ai/k8sgpt/commit/9bace02a6702a8af0e6511b51ffc38378e14d3cb))\n\n## [0.3.36](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.35...v0.3.36) (2024-06-17)\n\n\n### Other\n\n* **deps:** update docker/login-action digest to 0d4c9c5 ([#1141](https://github.com/k8sgpt-ai/k8sgpt/issues/1141)) ([602d111](https://github.com/k8sgpt-ai/k8sgpt/commit/602d111d8568d38cda744d2b179ee2d3eb59ba02))\n* **deps:** update goreleaser/goreleaser-action digest to 5742e2a ([#1153](https://github.com/k8sgpt-ai/k8sgpt/issues/1153)) ([55ae7c3](https://github.com/k8sgpt-ai/k8sgpt/commit/55ae7c32986100d4b0bab6dcaf7a52ac7b37aa5f))\n* fixed the goreleaser file ([#1158](https://github.com/k8sgpt-ai/k8sgpt/issues/1158)) ([2382de4](https://github.com/k8sgpt-ai/k8sgpt/commit/2382de4c6f82de535b67c2752d7c502d0a8b2b66))\n* update goreleaser ldflags ([#1154](https://github.com/k8sgpt-ai/k8sgpt/issues/1154)) ([aeae2ba](https://github.com/k8sgpt-ai/k8sgpt/commit/aeae2ba765c7db6e4953b5a93c54617f1dd85efa))\n\n## [0.3.35](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.34...v0.3.35) (2024-06-14)\n\n\n### Features\n\n* add spec.template.spec.securityContext  ([#1109](https://github.com/k8sgpt-ai/k8sgpt/issues/1109)) ([92dd1bd](https://github.com/k8sgpt-ai/k8sgpt/commit/92dd1bd8b08c5173f72a6c333f626c63aa05a1d3))\n* support openai organization Id ([#1133](https://github.com/k8sgpt-ai/k8sgpt/issues/1133)) ([4867d39](https://github.com/k8sgpt-ai/k8sgpt/commit/4867d39c66a6c16906cd769a2055dea9f66f1ccb))\n\n\n### Other\n\n* updated goreleaser config ([#1149](https://github.com/k8sgpt-ai/k8sgpt/issues/1149)) ([c834c09](https://github.com/k8sgpt-ai/k8sgpt/commit/c834c099969f3e888f49f73fba6794387063a6fc))\n\n## [0.3.34](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.33...v0.3.34) (2024-06-14)\n\n\n### Other\n\n* **deps:** update google-github-actions/release-please-action action to v4.1.1 ([#1143](https://github.com/k8sgpt-ai/k8sgpt/issues/1143)) ([63b63f7](https://github.com/k8sgpt-ai/k8sgpt/commit/63b63f7664277042188351073f269569bfec65bf))\n* **deps:** update goreleaser/goreleaser-action digest to 5742e2a ([#1142](https://github.com/k8sgpt-ai/k8sgpt/issues/1142)) ([c101e8a](https://github.com/k8sgpt-ai/k8sgpt/commit/c101e8a3ea6d911d00ca2a51986edc5425a1042a))\n\n## [0.3.33](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.32...v0.3.33) (2024-06-13)\n\n\n### Features\n\n* bump golang version to 1.22 ([#1117](https://github.com/k8sgpt-ai/k8sgpt/issues/1117)) ([6652fbe](https://github.com/k8sgpt-ai/k8sgpt/commit/6652fbe7cb6e581497e1d086e13397ff9e5b11be))\n\n\n### Bug Fixes\n\n* advisory k8sgpt ghsa 85rg 8m6h 825p ([#1139](https://github.com/k8sgpt-ai/k8sgpt/issues/1139)) ([728555c](https://github.com/k8sgpt-ai/k8sgpt/commit/728555c0effbf7a56221d625bcbbf62f74d14359))\n* **deps:** typo in prometheus.go ([fad00ea](https://github.com/k8sgpt-ai/k8sgpt/commit/fad00eac4925351c4dc6fd6dd347fe2968f0b7a5))\n* **deps:** typo in prometheus.go ([#1137](https://github.com/k8sgpt-ai/k8sgpt/issues/1137)) ([fad00ea](https://github.com/k8sgpt-ai/k8sgpt/commit/fad00eac4925351c4dc6fd6dd347fe2968f0b7a5))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.53.21 ([#1106](https://github.com/k8sgpt-ai/k8sgpt/issues/1106)) ([bdd470f](https://github.com/k8sgpt-ai/k8sgpt/commit/bdd470f9cae917f965badd22da7def4a7d64d2ae))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.6.0 [security] ([#1138](https://github.com/k8sgpt-ai/k8sgpt/issues/1138)) ([3a89318](https://github.com/k8sgpt-ai/k8sgpt/commit/3a893184af50f8c822ac06ce0e20818eaec587b1))\n\n\n### Other\n\n* **deps:** update actions/setup-go digest to cdcb360 ([#1096](https://github.com/k8sgpt-ai/k8sgpt/issues/1096)) ([3452c0d](https://github.com/k8sgpt-ai/k8sgpt/commit/3452c0def68fd5352d2d09201f813f657245bd9f))\n\n## [0.3.32](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.31...v0.3.32) (2024-05-20)\n\n\n### Bug Fixes\n\n* remove shorthand flag for topp option in add command ([#1115](https://github.com/k8sgpt-ai/k8sgpt/issues/1115)) ([e261c09](https://github.com/k8sgpt-ai/k8sgpt/commit/e261c09889359d5870acb9720ff033440f835f8f))\n\n## [0.3.31](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.30...v0.3.31) (2024-05-16)\n\n\n### Features\n\n* implement Top-K sampling for improved user control ([#1110](https://github.com/k8sgpt-ai/k8sgpt/issues/1110)) ([eda5231](https://github.com/k8sgpt-ai/k8sgpt/commit/eda52312aef8113debbd770b8354c3a3cb1cc681))\n* oci genai ([#1102](https://github.com/k8sgpt-ai/k8sgpt/issues/1102)) ([047afd4](https://github.com/k8sgpt-ai/k8sgpt/commit/047afd46d62d1bd1da1435550cbaf9daaca53aee))\n* support AWS_PROFILE ([#1114](https://github.com/k8sgpt-ai/k8sgpt/issues/1114)) ([882c6f5](https://github.com/k8sgpt-ai/k8sgpt/commit/882c6f52252000da436e4fed9fd184b263f5a017))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 0849a56 ([#1080](https://github.com/k8sgpt-ai/k8sgpt/issues/1080)) ([e894e77](https://github.com/k8sgpt-ai/k8sgpt/commit/e894e778e91d070448cd4a3f46dfc98dd588c9ed))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 to v2.19.1-20240406062209-1cc152efbf5c.1 ([#1070](https://github.com/k8sgpt-ai/k8sgpt/issues/1070)) ([24cff90](https://github.com/k8sgpt-ai/k8sgpt/commit/24cff90a0ca7488e48c94d13678529617c749aab))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20240406062209-1cc152efbf5c.3 ([#1086](https://github.com/k8sgpt-ai/k8sgpt/issues/1086)) ([820cd2e](https://github.com/k8sgpt-ai/k8sgpt/commit/820cd2e16cbca2c89b56a4d2a69f95f3f5cd6c6b))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.51.32 ([#1083](https://github.com/k8sgpt-ai/k8sgpt/issues/1083)) ([75c2add](https://github.com/k8sgpt-ai/k8sgpt/commit/75c2addf66a54df57d0c0ac17f0b359f7612e446))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.52.3 ([#1094](https://github.com/k8sgpt-ai/k8sgpt/issues/1094)) ([3c48231](https://github.com/k8sgpt-ai/k8sgpt/commit/3c4823127ca04d1d280da6d932e951e6c3f71536))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.5.2 ([#1084](https://github.com/k8sgpt-ai/k8sgpt/issues/1084)) ([bd695d0](https://github.com/k8sgpt-ai/k8sgpt/commit/bd695d0987e8ec12b44512c46bc5f2e5116076bd))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.3.2 ([#1085](https://github.com/k8sgpt-ai/k8sgpt/issues/1085)) ([43953ff](https://github.com/k8sgpt-ai/k8sgpt/commit/43953ffa3412ae97b6d54ed14b94955d1b73feba))\n* **deps:** update module github.com/cohere-ai/cohere-go/v2 to v2.7.3 ([#1087](https://github.com/k8sgpt-ai/k8sgpt/issues/1087)) ([36ccc62](https://github.com/k8sgpt-ai/k8sgpt/commit/36ccc628462ad102712fca115b56f521b2b33b38))\n* **deps:** update module github.com/google/generative-ai-go to v0.11.0 ([#1089](https://github.com/k8sgpt-ai/k8sgpt/issues/1089)) ([f30c9f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f30c9f555449bb90bf8242b88b8fae936cb57938))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.23.0 ([#1091](https://github.com/k8sgpt-ai/k8sgpt/issues/1091)) ([e74fc08](https://github.com/k8sgpt-ai/k8sgpt/commit/e74fc0838feac5a019a340f7c5ad1c9ae49913fa))\n* **deps:** update module golang.org/x/net to v0.25.0 ([#1092](https://github.com/k8sgpt-ai/k8sgpt/issues/1092)) ([fe53907](https://github.com/k8sgpt-ai/k8sgpt/commit/fe53907c44e9cd56b6747f52ae3402bc6ae2bd49))\n\n\n### Other\n\n* **deps:** pin codecov/codecov-action action to ab904c4 ([#1031](https://github.com/k8sgpt-ai/k8sgpt/issues/1031)) ([e0af76f](https://github.com/k8sgpt-ai/k8sgpt/commit/e0af76f3c9c0120dbc4d9373d69a262e1ec2b7f2))\n* **deps:** update actions/checkout digest to 0ad4b8f ([#1078](https://github.com/k8sgpt-ai/k8sgpt/issues/1078)) ([ea8183c](https://github.com/k8sgpt-ai/k8sgpt/commit/ea8183ce848ba58f91cfa68755d6f5b9cf695d36))\n* **deps:** update actions/upload-artifact digest to 6546280 ([#1079](https://github.com/k8sgpt-ai/k8sgpt/issues/1079)) ([9b797d7](https://github.com/k8sgpt-ai/k8sgpt/commit/9b797d7e8b4f704dae12acaa7778b6b65e2c36ac))\n* **deps:** update amannn/action-semantic-pull-request action to v5.5.2 ([#1088](https://github.com/k8sgpt-ai/k8sgpt/issues/1088)) ([a809a45](https://github.com/k8sgpt-ai/k8sgpt/commit/a809a455f55d1af104ebc0540007aa678581dd21))\n* **deps:** update anchore/sbom-action action to v0.15.11 ([#1082](https://github.com/k8sgpt-ai/k8sgpt/issues/1082)) ([12fa5ae](https://github.com/k8sgpt-ai/k8sgpt/commit/12fa5aef4dada597d7059e5717ec7bee3b38c122))\n* **deps:** update docker/build-push-action digest to 2cdde99 ([#1032](https://github.com/k8sgpt-ai/k8sgpt/issues/1032)) ([b12c006](https://github.com/k8sgpt-ai/k8sgpt/commit/b12c006c6304165269b90d770048b851e1aa1d1f))\n* **deps:** update google-github-actions/release-please-action action to v4.1.0 ([#1045](https://github.com/k8sgpt-ai/k8sgpt/issues/1045)) ([bf6f642](https://github.com/k8sgpt-ai/k8sgpt/commit/bf6f642c280f640f2c9020b325e52670ced2cf50))\n\n\n### Docs\n\n* add logAnalyzer in README.md ([#1081](https://github.com/k8sgpt-ai/k8sgpt/issues/1081)) ([5cfe332](https://github.com/k8sgpt-ai/k8sgpt/commit/5cfe3325cb556cfb9d0532ae26727441c5177015))\n\n## [0.3.30](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.29...v0.3.30) (2024-04-26)\n\n\n### Features\n\n* add keda integration ([#1058](https://github.com/k8sgpt-ai/k8sgpt/issues/1058)) ([9a73d19](https://github.com/k8sgpt-ai/k8sgpt/commit/9a73d1923f146aa1343465d89225e64bcb8e0112))\n* add minio support ([#1048](https://github.com/k8sgpt-ai/k8sgpt/issues/1048)) ([e6085d4](https://github.com/k8sgpt-ai/k8sgpt/commit/e6085d4191a1695e295f4f6a2ac7219b67a37225))\n* add Resource Kind in output ([#1069](https://github.com/k8sgpt-ai/k8sgpt/issues/1069)) ([aa276a5](https://github.com/k8sgpt-ai/k8sgpt/commit/aa276a5379b3d24a8e7a1f8b1193832df5a46220))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to 4693a02 ([#1037](https://github.com/k8sgpt-ai/k8sgpt/issues/1037)) ([94cdce4](https://github.com/k8sgpt-ai/k8sgpt/commit/94cdce44b49e0bb85e8b541688b2206e7c1dc33d))\n* **deps:** update module cloud.google.com/go/storage to v1.39.1 ([#1029](https://github.com/k8sgpt-ai/k8sgpt/issues/1029)) ([a3896f4](https://github.com/k8sgpt-ai/k8sgpt/commit/a3896f4518ec6666a43de22a24a18f2b93c58073))\n* **deps:** update module cloud.google.com/go/storage to v1.40.0 ([#1054](https://github.com/k8sgpt-ai/k8sgpt/issues/1054)) ([6df0169](https://github.com/k8sgpt-ai/k8sgpt/commit/6df01694916504cc4af3795361a4285098e2de85))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.51.14 ([#1051](https://github.com/k8sgpt-ai/k8sgpt/issues/1051)) ([007b4bb](https://github.com/k8sgpt-ai/k8sgpt/commit/007b4bb8ec4b36705f76fd2f5d96464c75915573))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.51.21 ([#1056](https://github.com/k8sgpt-ai/k8sgpt/issues/1056)) ([ccb692c](https://github.com/k8sgpt-ai/k8sgpt/commit/ccb692c1fdc5496d9d5810dfe41dbf1bdeb68d00))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.51.8 ([#1046](https://github.com/k8sgpt-ai/k8sgpt/issues/1046)) ([19ae31b](https://github.com/k8sgpt-ai/k8sgpt/commit/19ae31b5dd5c54413025cee8081d112223e38400))\n* **deps:** update module github.com/google/generative-ai-go to v0.10.0 ([#1047](https://github.com/k8sgpt-ai/k8sgpt/issues/1047)) ([6b38a56](https://github.com/k8sgpt-ai/k8sgpt/commit/6b38a56afbdaa8e0d8f025088a52d3022673ef9d))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.20.4 ([#1039](https://github.com/k8sgpt-ai/k8sgpt/issues/1039)) ([6a46a26](https://github.com/k8sgpt-ai/k8sgpt/commit/6a46a26789f730d298cf49a706421f36bc8523b1))\n* **deps:** update module golang.org/x/net to v0.23.0 [security] ([#1071](https://github.com/k8sgpt-ai/k8sgpt/issues/1071)) ([693b23f](https://github.com/k8sgpt-ai/k8sgpt/commit/693b23f1fc33659a3c4f52fc4d9c23348b22bfb1))\n* invalid ParentObj in output ([#1068](https://github.com/k8sgpt-ai/k8sgpt/issues/1068)) ([b2ab943](https://github.com/k8sgpt-ai/k8sgpt/commit/b2ab94375e4233cdfa9762877995445c313bb962))\n* remove show password in auth list ([#1061](https://github.com/k8sgpt-ai/k8sgpt/issues/1061)) ([9e02637](https://github.com/k8sgpt-ai/k8sgpt/commit/9e0263778f6dbc179184fa9d86f07d808283d63e))\n* set topP from config ([#1053](https://github.com/k8sgpt-ai/k8sgpt/issues/1053)) ([c162cc2](https://github.com/k8sgpt-ai/k8sgpt/commit/c162cc22ee468070e0602d3fd684b022fa585c4f))\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.15.10 ([#1044](https://github.com/k8sgpt-ai/k8sgpt/issues/1044)) ([e05a902](https://github.com/k8sgpt-ai/k8sgpt/commit/e05a902d904fc0b63998ae290f15e79d330317fb))\n* **deps:** update cohere client implementation to v2  ([#1062](https://github.com/k8sgpt-ai/k8sgpt/issues/1062)) ([eb7687a](https://github.com/k8sgpt-ai/k8sgpt/commit/eb7687a08917ad4048c6f00c17bb45591a935a3a))\n* **deps:** update docker/login-action digest to e92390c ([#1033](https://github.com/k8sgpt-ai/k8sgpt/issues/1033)) ([c872e49](https://github.com/k8sgpt-ai/k8sgpt/commit/c872e495ad6f787cf566a5b2f295deb3f08aba15))\n* **deps:** update docker/setup-buildx-action digest to 2b51285 ([#1036](https://github.com/k8sgpt-ai/k8sgpt/issues/1036)) ([10c00ba](https://github.com/k8sgpt-ai/k8sgpt/commit/10c00ba9fe61a3ee1dc90d87dd7997da276905b4))\n* **deps:** update docker/setup-buildx-action digest to d70bba7 ([#1066](https://github.com/k8sgpt-ai/k8sgpt/issues/1066)) ([3eaf776](https://github.com/k8sgpt-ai/k8sgpt/commit/3eaf776249719a0a13909d24e6b48deb6bf818b6))\n* update license file path to avoid conflicting installations ([#878](https://github.com/k8sgpt-ai/k8sgpt/issues/878)) ([#1073](https://github.com/k8sgpt-ai/k8sgpt/issues/1073)) ([85a76a3](https://github.com/k8sgpt-ai/k8sgpt/commit/85a76a3be06df0ff713192d1f08fd01d1e8f219b))\n* update renovate config and bundle deps in groups ([#1026](https://github.com/k8sgpt-ai/k8sgpt/issues/1026)) ([bd2e06b](https://github.com/k8sgpt-ai/k8sgpt/commit/bd2e06bae72528c5af1b4f44674d624d474d40dc))\n\n\n### Refactoring\n\n* replace util.SliceContainsString with slices.Contains & make fmt ([#1041](https://github.com/k8sgpt-ai/k8sgpt/issues/1041)) ([1ae4e75](https://github.com/k8sgpt-ai/k8sgpt/commit/1ae4e751967850e8146f8f3fa04c0dd302ef15bf))\n\n## [0.3.29](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.28...v0.3.29) (2024-03-22)\n\n\n### Features\n\n* codecov ([#1023](https://github.com/k8sgpt-ai/k8sgpt/issues/1023)) ([fe81d16](https://github.com/k8sgpt-ai/k8sgpt/commit/fe81d16f756e5ea9db909e42e6caf1e17e040f86))\n\n\n### Other\n\n* allows an environmental override of the default AWS region and… ([#1025](https://github.com/k8sgpt-ai/k8sgpt/issues/1025)) ([8f8f5c6](https://github.com/k8sgpt-ai/k8sgpt/commit/8f8f5c6df7fbcd08ee48d91a4f2e011a3e69e4ac))\n\n## [0.3.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.27...v0.3.28) (2024-03-14)\n\n\n### Features\n\n* add Google Vertex AI as provider to utilize gemini via GCP ([#984](https://github.com/k8sgpt-ai/k8sgpt/issues/984)) ([55ac0b2](https://github.com/k8sgpt-ai/k8sgpt/commit/55ac0b2129a438661a0253251f546db6b59f2b92))\n* add proxysettings for azureopenai and openai ([#987](https://github.com/k8sgpt-ai/k8sgpt/issues/987)) ([307710e](https://github.com/k8sgpt-ai/k8sgpt/commit/307710eddc1c3f96f40a674f7dda786510e9c4cc))\n* aws integration ([#967](https://github.com/k8sgpt-ai/k8sgpt/issues/967)) ([a81377f](https://github.com/k8sgpt-ai/k8sgpt/commit/a81377f72db7f322e0afbb6d613c2bfffecf8080))\n* enable Rest api using grpc-gateway ([#834](https://github.com/k8sgpt-ai/k8sgpt/issues/834)) ([f2138c7](https://github.com/k8sgpt-ai/k8sgpt/commit/f2138c71017b391625eebdfb4c5708c824824f69))\n\n\n### Bug Fixes\n\n* analyze command default backend bug ([#966](https://github.com/k8sgpt-ai/k8sgpt/issues/966)) ([aab8d77](https://github.com/k8sgpt-ai/k8sgpt/commit/aab8d77febdd4b42ff74aafbb2ada27745c04ae1))\n* **deps:** update module cloud.google.com/go/storage to v1.38.0 ([#950](https://github.com/k8sgpt-ai/k8sgpt/issues/950)) ([6207c70](https://github.com/k8sgpt-ai/k8sgpt/commit/6207c70c51d2885c4590c255c8f78e7ee2009034))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.50.20 ([#930](https://github.com/k8sgpt-ai/k8sgpt/issues/930)) ([3f0356b](https://github.com/k8sgpt-ai/k8sgpt/commit/3f0356be662c32d82ce4f3db05f859477823717d))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.50.21 ([#970](https://github.com/k8sgpt-ai/k8sgpt/issues/970)) ([00c91f0](https://github.com/k8sgpt-ai/k8sgpt/commit/00c91f05a62b2c8b2d756b58b95279195ff38d3d))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.50.22 ([#971](https://github.com/k8sgpt-ai/k8sgpt/issues/971)) ([6ac815c](https://github.com/k8sgpt-ai/k8sgpt/commit/6ac815c10fb073f4251e338ab22e247625f21406))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.50.34 ([#974](https://github.com/k8sgpt-ai/k8sgpt/issues/974)) ([425f33b](https://github.com/k8sgpt-ai/k8sgpt/commit/425f33bb2ddf8cdaff079b097d6956f675c89b0e))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.3.1 ([#992](https://github.com/k8sgpt-ai/k8sgpt/issues/992)) ([85f18dd](https://github.com/k8sgpt-ai/k8sgpt/commit/85f18dde1f820fe2413cc6b3109e67b7a010142c))\n* **deps:** update module github.com/google/generative-ai-go to v0.8.0 ([#965](https://github.com/k8sgpt-ai/k8sgpt/issues/965)) ([248260e](https://github.com/k8sgpt-ai/k8sgpt/commit/248260e081327de9f9d1d2c851efab2b4a3e7ede))\n* **deps:** update module github.com/prometheus/client_golang to v1.19.0 ([#989](https://github.com/k8sgpt-ai/k8sgpt/issues/989)) ([4065fae](https://github.com/k8sgpt-ai/k8sgpt/commit/4065faef13691f9cf1f50696c62d3b30b0933b4b))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.19.4 ([#963](https://github.com/k8sgpt-ai/k8sgpt/issues/963)) ([8b0b61e](https://github.com/k8sgpt-ai/k8sgpt/commit/8b0b61e596f790b9558a5e3d1f634a5ee1c6cb0c))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.20.0 ([#977](https://github.com/k8sgpt-ai/k8sgpt/issues/977)) ([e07822c](https://github.com/k8sgpt-ai/k8sgpt/commit/e07822c10bff5dbd91f4da592914c25538353d6b))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.20.1 ([#986](https://github.com/k8sgpt-ai/k8sgpt/issues/986)) ([88a7907](https://github.com/k8sgpt-ai/k8sgpt/commit/88a7907db4700c241e9aa109bc3d8604a8186f87))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.20.2 ([#991](https://github.com/k8sgpt-ai/k8sgpt/issues/991)) ([d2754d3](https://github.com/k8sgpt-ai/k8sgpt/commit/d2754d320fb1f285f93fdced2b8469280bd47fd2))\n* **deps:** update module github.com/schollz/progressbar/v3 to v3.14.2 ([#983](https://github.com/k8sgpt-ai/k8sgpt/issues/983)) ([af3732a](https://github.com/k8sgpt-ai/k8sgpt/commit/af3732ad067b809c54c5f08f6cf5a7a519b452d7))\n* **deps:** update module github.com/stretchr/testify to v1.9.0 ([#999](https://github.com/k8sgpt-ai/k8sgpt/issues/999)) ([1491e67](https://github.com/k8sgpt-ai/k8sgpt/commit/1491e675673dcc13ccf6ac1778113762542e8cbc))\n* **deps:** update module go.uber.org/zap to v1.27.0 ([#972](https://github.com/k8sgpt-ai/k8sgpt/issues/972)) ([8f00218](https://github.com/k8sgpt-ai/k8sgpt/commit/8f002180901c8bf7e6b1a5451dd97ef566260b0f))\n* **deps:** update module google.golang.org/api to v0.165.0 ([#959](https://github.com/k8sgpt-ai/k8sgpt/issues/959)) ([cc99bd5](https://github.com/k8sgpt-ai/k8sgpt/commit/cc99bd51f05db4e87f806ac58ee1cb7a83b25e4d))\n* **deps:** update module google.golang.org/api to v0.167.0 ([#973](https://github.com/k8sgpt-ai/k8sgpt/issues/973)) ([6103c96](https://github.com/k8sgpt-ai/k8sgpt/commit/6103c96c41e10e2fe13d285ff15a36bf2fbeb5c2))\n* **deps:** update module google.golang.org/grpc to v1.62.0 ([#975](https://github.com/k8sgpt-ai/k8sgpt/issues/975)) ([97446aa](https://github.com/k8sgpt-ai/k8sgpt/commit/97446aae079824d6556416314c0a27514088a667))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#957](https://github.com/k8sgpt-ai/k8sgpt/issues/957)) ([f929e7f](https://github.com/k8sgpt-ai/k8sgpt/commit/f929e7feea5931ddec77af49dd08937aca85fd49))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#979](https://github.com/k8sgpt-ai/k8sgpt/issues/979)) ([35f5185](https://github.com/k8sgpt-ai/k8sgpt/commit/35f51859140c78ce953443afcc27f77230287809))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#980](https://github.com/k8sgpt-ai/k8sgpt/issues/980)) ([334a86a](https://github.com/k8sgpt-ai/k8sgpt/commit/334a86aaf40e5421929cf380191841db064d9bf7))\n* log analyzer failed with multiple containers in the pod ([#920](https://github.com/k8sgpt-ai/k8sgpt/issues/920)) ([98286a9](https://github.com/k8sgpt-ai/k8sgpt/commit/98286a965e4c4c680deeb43d3397b51089968366))\n* set result name and namespace to trivy vulnreport and configaudi… ([#869](https://github.com/k8sgpt-ai/k8sgpt/issues/869)) ([a3cd7e6](https://github.com/k8sgpt-ai/k8sgpt/commit/a3cd7e6385365a1d190a9e8439311cb9d5eeda56))\n* shorthand for the http flag in serve command ([#969](https://github.com/k8sgpt-ai/k8sgpt/issues/969)) ([f55f837](https://github.com/k8sgpt-ai/k8sgpt/commit/f55f8370ebf0db6db629641337cd78ad7f120865))\n\n\n### Other\n\n* attempt to group renovate deps ([#1007](https://github.com/k8sgpt-ai/k8sgpt/issues/1007)) ([adf4f17](https://github.com/k8sgpt-ai/k8sgpt/commit/adf4f17085672fd5ae78dad4f8ac1d887029836d))\n* **deps:** update anchore/sbom-action action to v0.15.9 ([#1004](https://github.com/k8sgpt-ai/k8sgpt/issues/1004)) ([b05b6a3](https://github.com/k8sgpt-ai/k8sgpt/commit/b05b6a38ed4a9fc017f9dcb52cff8a332c11056d))\n* **deps:** update docker/build-push-action digest to af5a7ed ([#1003](https://github.com/k8sgpt-ai/k8sgpt/issues/1003)) ([b58b719](https://github.com/k8sgpt-ai/k8sgpt/commit/b58b7191af2fe082d94d46ef6a2784c1ea322340))\n* **deps:** update docker/setup-buildx-action digest to 0d103c3 ([#988](https://github.com/k8sgpt-ai/k8sgpt/issues/988)) ([f24bcd8](https://github.com/k8sgpt-ai/k8sgpt/commit/f24bcd88b6a915798897b49a562b86265a9b524c))\n* **deps:** update reviewdog/action-golangci-lint digest to 00311c2 ([#1002](https://github.com/k8sgpt-ai/k8sgpt/issues/1002)) ([4ec143a](https://github.com/k8sgpt-ai/k8sgpt/commit/4ec143ab772ca4dc3072c248e95da8f7c0a2974b))\n\n## [0.3.27](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.26...v0.3.27) (2024-02-15)\n\n\n### Features\n\n* add huggingface provider ([#893](https://github.com/k8sgpt-ai/k8sgpt/issues/893)) ([2fd476e](https://github.com/k8sgpt-ai/k8sgpt/commit/2fd476e12624e30570c0819594f2668f720381d6))\n* added FailedMount event reason to get the failure ([#883](https://github.com/k8sgpt-ai/k8sgpt/issues/883)) ([78126b2](https://github.com/k8sgpt-ai/k8sgpt/commit/78126b2328c1b3f81a269d203e86128104050010))\n* enables remote custom analyzers ([#906](https://github.com/k8sgpt-ai/k8sgpt/issues/906)) ([c8c9dbf](https://github.com/k8sgpt-ai/k8sgpt/commit/c8c9dbfadc72a193ab9f3431d02d50ac5ab5d071))\n\n\n### Bug Fixes\n\n* **deps:** update k8s.io/utils digest to e7106e6 ([#897](https://github.com/k8sgpt-ai/k8sgpt/issues/897)) ([28c4c57](https://github.com/k8sgpt-ai/k8sgpt/commit/28c4c57e4566b9b888a5633090ccb70875d30106))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20240128172516-6bf6a55ff115.2 ([#899](https://github.com/k8sgpt-ai/k8sgpt/issues/899)) ([e3eee6d](https://github.com/k8sgpt-ai/k8sgpt/commit/e3eee6d9566a59fd62e6bb804257b1383f75e3ef))\n* **deps:** update module cloud.google.com/go/storage to v1.37.0 ([#934](https://github.com/k8sgpt-ai/k8sgpt/issues/934)) ([3d2554b](https://github.com/k8sgpt-ai/k8sgpt/commit/3d2554b9cd8817b24cf8858a107420d6d8424aa4))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.21 ([#868](https://github.com/k8sgpt-ai/k8sgpt/issues/868)) ([88002e7](https://github.com/k8sgpt-ai/k8sgpt/commit/88002e7e8c3e9c71365c44e136a6f1a8d35e1744))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.50.2 ([#887](https://github.com/k8sgpt-ai/k8sgpt/issues/887)) ([817d9cf](https://github.com/k8sgpt-ai/k8sgpt/commit/817d9cf754d307d374befc0d57919eb7a0183aaf))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.5.1 ([#939](https://github.com/k8sgpt-ai/k8sgpt/issues/939)) ([ce7c955](https://github.com/k8sgpt-ai/k8sgpt/commit/ce7c9551bcb1a8b24922a1eb062605bbfeec7929))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.3.0 ([#952](https://github.com/k8sgpt-ai/k8sgpt/issues/952)) ([fea2ed1](https://github.com/k8sgpt-ai/k8sgpt/commit/fea2ed1fff5fb5a46d6abc2feb72e1e1adf3b69b))\n* **deps:** update module github.com/google/generative-ai-go to v0.7.0 ([#940](https://github.com/k8sgpt-ai/k8sgpt/issues/940)) ([3c8d9d4](https://github.com/k8sgpt-ai/k8sgpt/commit/3c8d9d42e573f27185a1572d1bc06f8af87f3a0b))\n* **deps:** update module github.com/prometheus/prometheus to v2 ([#863](https://github.com/k8sgpt-ai/k8sgpt/issues/863)) ([a253af2](https://github.com/k8sgpt-ai/k8sgpt/commit/a253af23b601b23179be5019fbb832a41423cdae))\n* **deps:** update module github.com/pterm/pterm to v0.12.75 ([#881](https://github.com/k8sgpt-ai/k8sgpt/issues/881)) ([e7d690a](https://github.com/k8sgpt-ai/k8sgpt/commit/e7d690afd12cb71d7b344ba92bf059ae18a993c8))\n* **deps:** update module github.com/pterm/pterm to v0.12.78 ([#890](https://github.com/k8sgpt-ai/k8sgpt/issues/890)) ([f9c1b90](https://github.com/k8sgpt-ai/k8sgpt/commit/f9c1b903385978be56f9c4bc87089bd1c761bbea))\n* **deps:** update module github.com/pterm/pterm to v0.12.79 ([#943](https://github.com/k8sgpt-ai/k8sgpt/issues/943)) ([bfbb5c7](https://github.com/k8sgpt-ai/k8sgpt/commit/bfbb5c7e03cad144f6037c7233ffc0817fd403e4))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.18.1 ([#871](https://github.com/k8sgpt-ai/k8sgpt/issues/871)) ([6c62c1a](https://github.com/k8sgpt-ai/k8sgpt/commit/6c62c1a0fcd38cf9de8a99cda6f37b221740b9c8))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.18.2 ([#874](https://github.com/k8sgpt-ai/k8sgpt/issues/874)) ([4de1bbd](https://github.com/k8sgpt-ai/k8sgpt/commit/4de1bbd6f72ca83d46ce5955bac50dffc99af03d))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.19.2 ([#886](https://github.com/k8sgpt-ai/k8sgpt/issues/886)) ([c601972](https://github.com/k8sgpt-ai/k8sgpt/commit/c6019728aea837884620e0b4894568802a948a6e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.19.3 ([#937](https://github.com/k8sgpt-ai/k8sgpt/issues/937)) ([f2eb1ef](https://github.com/k8sgpt-ai/k8sgpt/commit/f2eb1ef5334877fd3a26dda8c92023f831ea857e))\n* **deps:** update module golang.org/x/term to v0.17.0 ([#941](https://github.com/k8sgpt-ai/k8sgpt/issues/941)) ([4e57088](https://github.com/k8sgpt-ai/k8sgpt/commit/4e57088a0137767a42c778a59ff07fff04c04289))\n* **deps:** update module google.golang.org/api to v0.157.0 ([#860](https://github.com/k8sgpt-ai/k8sgpt/issues/860)) ([72e08ef](https://github.com/k8sgpt-ai/k8sgpt/commit/72e08efff1fc501dfcba791c9d940e575f3e2395))\n* **deps:** update module google.golang.org/api to v0.164.0 ([#953](https://github.com/k8sgpt-ai/k8sgpt/issues/953)) ([29b482f](https://github.com/k8sgpt-ai/k8sgpt/commit/29b482f5978795fa8db729030bd75803e2e61f95))\n* **deps:** update module google.golang.org/grpc to v1.61.1 ([#954](https://github.com/k8sgpt-ai/k8sgpt/issues/954)) ([9c1f1b8](https://github.com/k8sgpt-ai/k8sgpt/commit/9c1f1b8804a26f549379efe637d0bedb8e2cb890))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#866](https://github.com/k8sgpt-ai/k8sgpt/issues/866)) ([81d6604](https://github.com/k8sgpt-ai/k8sgpt/commit/81d660447d236cd03b75866871bb69f2c77c5c66))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#875](https://github.com/k8sgpt-ai/k8sgpt/issues/875)) ([1f371e2](https://github.com/k8sgpt-ai/k8sgpt/commit/1f371e2807c47dbb4613bf873ec67a77e8e6c80c))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#956](https://github.com/k8sgpt-ai/k8sgpt/issues/956)) ([d9fe744](https://github.com/k8sgpt-ai/k8sgpt/commit/d9fe7446af428209610adc83ec17cf50491a5a47))\n* lint errors ([#923](https://github.com/k8sgpt-ai/k8sgpt/issues/923)) ([3415031](https://github.com/k8sgpt-ai/k8sgpt/commit/3415031006bb5899019e68d33ac6083d03ef864b))\n* typo in httproute files name ([#877](https://github.com/k8sgpt-ai/k8sgpt/issues/877)) ([cdbeb14](https://github.com/k8sgpt-ai/k8sgpt/commit/cdbeb146a28ebc21ac2c4d27e977b1771f9290b4))\n* unused variable failure warning in webhooks file ([#916](https://github.com/k8sgpt-ai/k8sgpt/issues/916)) ([3f0964a](https://github.com/k8sgpt-ai/k8sgpt/commit/3f0964ad385390f53516904219fbfc47b989d31f))\n\n\n### Other\n\n* **deps:** update actions/upload-artifact digest to 26f96df ([#888](https://github.com/k8sgpt-ai/k8sgpt/issues/888)) ([483a9da](https://github.com/k8sgpt-ai/k8sgpt/commit/483a9dad103ad1af82491dc1d5e0a39bb4865a1b))\n* **deps:** update actions/upload-artifact digest to 5d5d22a ([#925](https://github.com/k8sgpt-ai/k8sgpt/issues/925)) ([070aa7f](https://github.com/k8sgpt-ai/k8sgpt/commit/070aa7fdd0982c0c7f02a1da9e6797d5efaa5586))\n* **deps:** update actions/upload-artifact digest to 694cdab ([#880](https://github.com/k8sgpt-ai/k8sgpt/issues/880)) ([3cf18e7](https://github.com/k8sgpt-ai/k8sgpt/commit/3cf18e783edb341b7bdd6aa20dbcce11971fa241))\n* **deps:** update anchore/sbom-action action to v0.15.4 ([#879](https://github.com/k8sgpt-ai/k8sgpt/issues/879)) ([d213399](https://github.com/k8sgpt-ai/k8sgpt/commit/d2133991617697b13b8846f2acb3a3bb6cebb160))\n* **deps:** update anchore/sbom-action action to v0.15.5 ([#885](https://github.com/k8sgpt-ai/k8sgpt/issues/885)) ([60853fe](https://github.com/k8sgpt-ai/k8sgpt/commit/60853fe4eb8de7a1fdbaea388c3d2d6205e273a6))\n* **deps:** update anchore/sbom-action action to v0.15.8 ([#926](https://github.com/k8sgpt-ai/k8sgpt/issues/926)) ([f61c3e2](https://github.com/k8sgpt-ai/k8sgpt/commit/f61c3e228c69fa160735ddb2c1347720112b738f))\n* **deps:** update golang docker tag to v1.22 ([#931](https://github.com/k8sgpt-ai/k8sgpt/issues/931)) ([37228d8](https://github.com/k8sgpt-ai/k8sgpt/commit/37228d88e357c66c5574559ae27a52fdf28418b8))\n* **deps:** update reviewdog/action-golangci-lint digest to 8e1117c ([#915](https://github.com/k8sgpt-ai/k8sgpt/issues/915)) ([599be33](https://github.com/k8sgpt-ai/k8sgpt/commit/599be33f38ad1fd688b8e7824102a7944d516435))\n* **deps:** update reviewdog/action-golangci-lint digest to f016e79 ([#714](https://github.com/k8sgpt-ai/k8sgpt/issues/714)) ([335616c](https://github.com/k8sgpt-ai/k8sgpt/commit/335616c20f7f8d9fefab4976d986a8d3b4867111))\n* grpc update ([#938](https://github.com/k8sgpt-ai/k8sgpt/issues/938)) ([bbf61f5](https://github.com/k8sgpt-ai/k8sgpt/commit/bbf61f53d4fb9244b5a79ae953370296ca9fd44b))\n* improve codebase and doc quality ([#922](https://github.com/k8sgpt-ai/k8sgpt/issues/922)) ([d97dea2](https://github.com/k8sgpt-ai/k8sgpt/commit/d97dea289681cd061ca0796208c50720bdb08914))\n* linting improvements and catching false positives ([#882](https://github.com/k8sgpt-ai/k8sgpt/issues/882)) ([2effbb3](https://github.com/k8sgpt-ai/k8sgpt/commit/2effbb345ad1c2771ec798e06ccde68d3253b4bc))\n* set correct license during package build ([#872](https://github.com/k8sgpt-ai/k8sgpt/issues/872)) ([42be51b](https://github.com/k8sgpt-ai/k8sgpt/commit/42be51bc8f625a35b1435c461d9a32c3c4905f1c))\n* updated deps ([#951](https://github.com/k8sgpt-ai/k8sgpt/issues/951)) ([015bccf](https://github.com/k8sgpt-ai/k8sgpt/commit/015bccfc2eae587e0ade371211404f5af4c37d27))\n\n## [0.3.26](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.25...v0.3.26) (2024-01-14)\n\n\n### Features\n\n* initial Prometheus analyzers ([#855](https://github.com/k8sgpt-ai/k8sgpt/issues/855)) ([45fa827](https://github.com/k8sgpt-ai/k8sgpt/commit/45fa827c046b91d901a08bec1a892d9c0917f350))\n* interactive mode ([#854](https://github.com/k8sgpt-ai/k8sgpt/issues/854)) ([9da75e0](https://github.com/k8sgpt-ai/k8sgpt/commit/9da75e02bc17146898377e4f90b7f59c5a8e0eee))\n* unify aiClientName const for all providers ([#848](https://github.com/k8sgpt-ai/k8sgpt/issues/848)) ([5c17c24](https://github.com/k8sgpt-ai/k8sgpt/commit/5c17c240550609d9fb7771fe67fe1ab19660b4da))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.16 ([#847](https://github.com/k8sgpt-ai/k8sgpt/issues/847)) ([ce4910b](https://github.com/k8sgpt-ai/k8sgpt/commit/ce4910bc5d064f80076877d7a096fff903308b63))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.17 ([#852](https://github.com/k8sgpt-ai/k8sgpt/issues/852)) ([85ebd12](https://github.com/k8sgpt-ai/k8sgpt/commit/85ebd12c30d369c5ef9a42b5a834d091523a7b6e))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.18 ([#856](https://github.com/k8sgpt-ai/k8sgpt/issues/856)) ([4106d39](https://github.com/k8sgpt-ai/k8sgpt/commit/4106d39c322940413ebfd9ac0bf6f5bd31830e93))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.19 ([#859](https://github.com/k8sgpt-ai/k8sgpt/issues/859)) ([6a2f315](https://github.com/k8sgpt-ai/k8sgpt/commit/6a2f315b2f4344f2924b7915e8a1393f9732a1e9))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.11 ([#853](https://github.com/k8sgpt-ai/k8sgpt/issues/853)) ([1979c86](https://github.com/k8sgpt-ai/k8sgpt/commit/1979c86d0f59921d55cd4229a37d604a6f1dc578))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.11 ([#861](https://github.com/k8sgpt-ai/k8sgpt/issues/861)) ([40b5b7e](https://github.com/k8sgpt-ai/k8sgpt/commit/40b5b7e185c8d335bdefb131988b9900ad26bac3))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#864](https://github.com/k8sgpt-ai/k8sgpt/issues/864)) ([36ba6c5](https://github.com/k8sgpt-ai/k8sgpt/commit/36ba6c5147a9ed75c14dbba4bc06cae903e651a4))\n* **deps:** update module gopkg.in/yaml.v2 to v3 ([#865](https://github.com/k8sgpt-ai/k8sgpt/issues/865)) ([c55025d](https://github.com/k8sgpt-ai/k8sgpt/commit/c55025d04ebf9da0f6092aabb0b043ccef05164c))\n\n\n### Other\n\n* **deps:** update actions/upload-artifact digest to 1eb3cb2 ([#867](https://github.com/k8sgpt-ai/k8sgpt/issues/867)) ([4ce56f3](https://github.com/k8sgpt-ai/k8sgpt/commit/4ce56f38b4338a6a2fe69f588b0f17e0b54d0ae6))\n* **deps:** update anchore/sbom-action action to v0.15.3 ([#850](https://github.com/k8sgpt-ai/k8sgpt/issues/850)) ([12f764d](https://github.com/k8sgpt-ai/k8sgpt/commit/12f764d5846accbd987d40f69a153dceb9954f39))\n\n\n### Docs\n\n* adjusted README information about providers ([#844](https://github.com/k8sgpt-ai/k8sgpt/issues/844)) ([745e960](https://github.com/k8sgpt-ai/k8sgpt/commit/745e960f492e6dd0e50aa4a1ce7239c677025024))\n\n## [0.3.25](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.24...v0.3.25) (2024-01-05)\n\n\n### Features\n\n* added Google GenAI client; simplified IAI/clients API surface. ([#829](https://github.com/k8sgpt-ai/k8sgpt/issues/829)) ([e7d4149](https://github.com/k8sgpt-ai/k8sgpt/commit/e7d41496ddaa145c70079852da8b2ce3b3b7289f))\n* code_cov badge ([#821](https://github.com/k8sgpt-ai/k8sgpt/issues/821)) ([fcd29a5](https://github.com/k8sgpt-ai/k8sgpt/commit/fcd29a547d73ba48935762e2f568f5755f5c6ed3))\n* coverage reports ([#819](https://github.com/k8sgpt-ai/k8sgpt/issues/819)) ([3d0ba3e](https://github.com/k8sgpt-ai/k8sgpt/commit/3d0ba3e78cabaf5f1262c5b5b16ebabad974fa87))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.10 ([#811](https://github.com/k8sgpt-ai/k8sgpt/issues/811)) ([e5cc4a2](https://github.com/k8sgpt-ai/k8sgpt/commit/e5cc4a28cb3682e7094e6ceddf91b65da991ddb6))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.12 ([#813](https://github.com/k8sgpt-ai/k8sgpt/issues/813)) ([91613ba](https://github.com/k8sgpt-ai/k8sgpt/commit/91613baa5cc5244c93deb344abcdd905802eef30))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.14 ([#822](https://github.com/k8sgpt-ai/k8sgpt/issues/822)) ([526e22f](https://github.com/k8sgpt-ai/k8sgpt/commit/526e22f88b8de15eceb10965b045ef0366ff2d6c))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.15 ([#835](https://github.com/k8sgpt-ai/k8sgpt/issues/835)) ([e78ff05](https://github.com/k8sgpt-ai/k8sgpt/commit/e78ff054190cd54cabe17d77ac69443e517f1e55))\n* **deps:** update module github.com/prometheus/client_golang to v1.18.0 ([#814](https://github.com/k8sgpt-ai/k8sgpt/issues/814)) ([6eb8f67](https://github.com/k8sgpt-ai/k8sgpt/commit/6eb8f6793ed989ba3ac7ed00336345f68b09bf45))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.10 ([#824](https://github.com/k8sgpt-ai/k8sgpt/issues/824)) ([4314804](https://github.com/k8sgpt-ai/k8sgpt/commit/4314804ca7e782f5149dc2078ba9c859edc4688a))\n* **deps:** update module golang.org/x/term to v0.16.0 ([#831](https://github.com/k8sgpt-ai/k8sgpt/issues/831)) ([4de989c](https://github.com/k8sgpt-ai/k8sgpt/commit/4de989c803ee43a02d75112d1b3a54daee3dd9af))\n* **deps:** update module google.golang.org/api to v0.155.0 ([#836](https://github.com/k8sgpt-ai/k8sgpt/issues/836)) ([105a239](https://github.com/k8sgpt-ai/k8sgpt/commit/105a239d94384f4096c01d9978564040773ab56e))\n* no explain case, improved readability. ([#825](https://github.com/k8sgpt-ai/k8sgpt/issues/825)) ([035348d](https://github.com/k8sgpt-ai/k8sgpt/commit/035348d8a0d290ac26b42425945eaafe038cedc5))\n\n\n### Other\n\n* added basic server startup test ([#817](https://github.com/k8sgpt-ai/k8sgpt/issues/817)) ([3e7cea7](https://github.com/k8sgpt-ai/k8sgpt/commit/3e7cea7bd39253718bc3d2f8b10ac5fc9b98cbc2))\n* **deps:** pin codecov/codecov-action action to eaaf4be ([#820](https://github.com/k8sgpt-ai/k8sgpt/issues/820)) ([2f0f2df](https://github.com/k8sgpt-ai/k8sgpt/commit/2f0f2dfa8a5957cb8b10864c14d7883158723a6a))\n* **deps:** update anchore/sbom-action action to v0.15.2 ([#823](https://github.com/k8sgpt-ai/k8sgpt/issues/823)) ([70c6892](https://github.com/k8sgpt-ai/k8sgpt/commit/70c68929d8d963c0bd17390c76e366d4339f56b9))\n* lint fixes ([#833](https://github.com/k8sgpt-ai/k8sgpt/issues/833)) ([a7e9b48](https://github.com/k8sgpt-ai/k8sgpt/commit/a7e9b486bad7c2d62878e470a755d1fef3803680))\n* remove code cov ([#832](https://github.com/k8sgpt-ai/k8sgpt/issues/832)) ([a774265](https://github.com/k8sgpt-ai/k8sgpt/commit/a77426593d7f3a8cfa810336ff08a2266db7fb4f))\n\n\n### Dependency Updates\n\n* go module bump to fix CVE: GHSA-45x7-px36-x8w8 & GHSA-7ww5-4wqc-m92c ([#810](https://github.com/k8sgpt-ai/k8sgpt/issues/810)) ([b17fd7c](https://github.com/k8sgpt-ai/k8sgpt/commit/b17fd7c98644afa70d414fcb32e49e61e1c831ad))\n\n## [0.3.24](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.23...v0.3.24) (2023-12-23)\n\n\n### Features\n\n* add last termination state when pod is in CrashloopBackoff ([#792](https://github.com/k8sgpt-ai/k8sgpt/issues/792)) ([ff4aaf7](https://github.com/k8sgpt-ai/k8sgpt/commit/ff4aaf7c328a58fcad8e4fb0f93ea543725eedd5))\n* Add license scan report and status ([#796](https://github.com/k8sgpt-ai/k8sgpt/issues/796)) ([343aec8](https://github.com/k8sgpt-ai/k8sgpt/commit/343aec8f0455c9461eb8d495ca5bd446b4bad667))\n* version upgrade to 1.21 ([#798](https://github.com/k8sgpt-ai/k8sgpt/issues/798)) ([c23f24d](https://github.com/k8sgpt-ai/k8sgpt/commit/c23f24de2e79347e4f5465e28af34e138cc13231))\n\n\n### Bug Fixes\n\n* added the ability to set the trivy variables by the user ([#797](https://github.com/k8sgpt-ai/k8sgpt/issues/797)) ([928b39a](https://github.com/k8sgpt-ai/k8sgpt/commit/928b39a7283ee274dd517e727624eceb3795594d))\n* **deps:** update module cloud.google.com/go/storage to v1.36.0 ([#805](https://github.com/k8sgpt-ai/k8sgpt/issues/805)) ([390f309](https://github.com/k8sgpt-ai/k8sgpt/commit/390f30908800dfe21e2c1660139b0bd9d36b34d6))\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.17.1 ([#780](https://github.com/k8sgpt-ai/k8sgpt/issues/780)) ([71f36bd](https://github.com/k8sgpt-ai/k8sgpt/commit/71f36bdb0b3729c4357299b7d03829dd5b6a69ec))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.6 ([#783](https://github.com/k8sgpt-ai/k8sgpt/issues/783)) ([1b386f6](https://github.com/k8sgpt-ai/k8sgpt/commit/1b386f64f2863d8a49f423ad571cba009807bc55))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.7 ([#804](https://github.com/k8sgpt-ai/k8sgpt/issues/804)) ([3c6c759](https://github.com/k8sgpt-ai/k8sgpt/commit/3c6c7597e014bfd68794b1764c3a8902e8a798ea))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.8 ([#807](https://github.com/k8sgpt-ai/k8sgpt/issues/807)) ([93b5ca1](https://github.com/k8sgpt-ai/k8sgpt/commit/93b5ca1985c3730592388ba6fc32ecca9b806888))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.49.9 ([#808](https://github.com/k8sgpt-ai/k8sgpt/issues/808)) ([130e4c2](https://github.com/k8sgpt-ai/k8sgpt/commit/130e4c2efd0e5b34cdc84c357c6c1f3987cf7c35))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.2.1 ([#801](https://github.com/k8sgpt-ai/k8sgpt/issues/801)) ([aa05756](https://github.com/k8sgpt-ai/k8sgpt/commit/aa057565b5c971c493443f3ede4aed8f8a6399f7))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.5 ([#802](https://github.com/k8sgpt-ai/k8sgpt/issues/802)) ([4a7bad3](https://github.com/k8sgpt-ai/k8sgpt/commit/4a7bad313b66750bd830413b7fef005580ad843c))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.9 ([#772](https://github.com/k8sgpt-ai/k8sgpt/issues/772)) ([13d64a5](https://github.com/k8sgpt-ai/k8sgpt/commit/13d64a58750c7262c07042b557fbf2c4a511b777))\n* **deps:** update module github.com/spf13/viper to v1.18.2 ([#787](https://github.com/k8sgpt-ai/k8sgpt/issues/787)) ([8dea617](https://github.com/k8sgpt-ai/k8sgpt/commit/8dea6170a2c00c03f08f25e4f0a232be617536f1))\n* **deps:** update module google.golang.org/api to v0.154.0 ([#779](https://github.com/k8sgpt-ai/k8sgpt/issues/779)) ([78f7f2b](https://github.com/k8sgpt-ai/k8sgpt/commit/78f7f2ba85fd357cab13ccc15e9e767e8611773a))\n* **deps:** update module google.golang.org/grpc to v1.60.1 ([#790](https://github.com/k8sgpt-ai/k8sgpt/issues/790)) ([5d54c3f](https://github.com/k8sgpt-ai/k8sgpt/commit/5d54c3f840a9ce002606b6601187e69fb62f8a28))\n* **deps:** update module helm.sh/helm/v3 to v3.13.3 ([#803](https://github.com/k8sgpt-ai/k8sgpt/issues/803)) ([a8e1932](https://github.com/k8sgpt-ai/k8sgpt/commit/a8e193212222811f3a278df6056dd2165c4323bd))\n* lowercase logs before running regex matching in LogAnalyzer ([#794](https://github.com/k8sgpt-ai/k8sgpt/issues/794)) ([03b63be](https://github.com/k8sgpt-ai/k8sgpt/commit/03b63befa247ac84b795a0ec8d5280196b8d570d))\n\n\n### Other\n\n* **deps:** update actions/setup-go action to v5 ([#788](https://github.com/k8sgpt-ai/k8sgpt/issues/788)) ([d00ed33](https://github.com/k8sgpt-ai/k8sgpt/commit/d00ed33678b1560a3996f1d735d84ca0ca05c0b0))\n* **deps:** update actions/upload-artifact action to v4 ([#806](https://github.com/k8sgpt-ai/k8sgpt/issues/806)) ([d6fb648](https://github.com/k8sgpt-ai/k8sgpt/commit/d6fb648e23c1ed1e4680fc4b7b4e96501f50ad48))\n* **deps:** update anchore/sbom-action action to v0.15.1 ([#784](https://github.com/k8sgpt-ai/k8sgpt/issues/784)) ([6473a2b](https://github.com/k8sgpt-ai/k8sgpt/commit/6473a2b532491b707b3af922fc2198e626ebf219))\n* **deps:** update google-github-actions/release-please-action action to v4 ([#782](https://github.com/k8sgpt-ai/k8sgpt/issues/782)) ([2c28c55](https://github.com/k8sgpt-ai/k8sgpt/commit/2c28c555cf4e891b90ebd9e9eae1cd8724e9886f))\n* **deps:** update google-github-actions/release-please-action action to v4.0.2 ([#800](https://github.com/k8sgpt-ai/k8sgpt/issues/800)) ([be4b0bb](https://github.com/k8sgpt-ai/k8sgpt/commit/be4b0bb3c24e04d35f40d16fd8e94ddbc8457ca6))\n\n\n### Refactoring\n\n* replace rest client with controller-runtime clientset for Trivy analyzers ([#776](https://github.com/k8sgpt-ai/k8sgpt/issues/776)) ([1d19628](https://github.com/k8sgpt-ai/k8sgpt/commit/1d196286b75f0ea6c068e8bdb01455fb36c52432))\n\n## [0.3.23](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.22...v0.3.23) (2023-11-24)\n\n\n### Features\n\n* add Gateway analysers ([#764](https://github.com/k8sgpt-ai/k8sgpt/issues/764)) ([ec08cac](https://github.com/k8sgpt-ai/k8sgpt/commit/ec08cac21496b34b123b75b06d9283eb6539e890))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.48.3 ([#768](https://github.com/k8sgpt-ai/k8sgpt/issues/768)) ([b1c791a](https://github.com/k8sgpt-ai/k8sgpt/commit/b1c791a396b7287ef916e8f8d382a0e14ba39949))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.4 ([#767](https://github.com/k8sgpt-ai/k8sgpt/issues/767)) ([dca5b47](https://github.com/k8sgpt-ai/k8sgpt/commit/dca5b4710d1bb35dfc3346219d3bddb7c726300e))\n\n## [0.3.22](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.21...v0.3.22) (2023-11-21)\n\n\n### Features\n\n* rework cache package - add gcs cache - add cache purge command ([#750](https://github.com/k8sgpt-ai/k8sgpt/issues/750)) ([12146bf](https://github.com/k8sgpt-ai/k8sgpt/commit/12146bf356a3b26176c47e3a013a713fd14f346d))\n\n\n### Bug Fixes\n\n* cover more error reason messages ([#759](https://github.com/k8sgpt-ai/k8sgpt/issues/759)) ([5b27c3e](https://github.com/k8sgpt-ai/k8sgpt/commit/5b27c3e352701819f1d0449df9acf706040f1f13))\n* **deps:** update kubernetes packages to v0.28.4 ([#756](https://github.com/k8sgpt-ai/k8sgpt/issues/756)) ([24132c2](https://github.com/k8sgpt-ai/k8sgpt/commit/24132c2d87024157009589cf2bd410bac2a26241))\n* **deps:** update module cloud.google.com/go/storage to v1.35.1 ([#762](https://github.com/k8sgpt-ai/k8sgpt/issues/762)) ([58d182e](https://github.com/k8sgpt-ai/k8sgpt/commit/58d182e94f75f9b035a9e45159fa87ce8a57de38))\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.16.4 ([#676](https://github.com/k8sgpt-ai/k8sgpt/issues/676)) ([4531278](https://github.com/k8sgpt-ai/k8sgpt/commit/45312788c3c15e141027c3fc8e428cfaa71d3ace))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.10 ([#751](https://github.com/k8sgpt-ai/k8sgpt/issues/751)) ([2aa31bc](https://github.com/k8sgpt-ai/k8sgpt/commit/2aa31bc66d239906b1047f53bcaa58b0c30a2856))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.11 ([#752](https://github.com/k8sgpt-ai/k8sgpt/issues/752)) ([531fa79](https://github.com/k8sgpt-ai/k8sgpt/commit/531fa79ed640846b177c516559dc82f088fa940f))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.48.0 ([#754](https://github.com/k8sgpt-ai/k8sgpt/issues/754)) ([e2bb567](https://github.com/k8sgpt-ai/k8sgpt/commit/e2bb567d2f8d59a904583309c2774d4174eb367f))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.48.1 ([#766](https://github.com/k8sgpt-ai/k8sgpt/issues/766)) ([16469c0](https://github.com/k8sgpt-ai/k8sgpt/commit/16469c01c962fd5bfa4ad11dd88a41f3e00e4a0d))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.6 ([#749](https://github.com/k8sgpt-ai/k8sgpt/issues/749)) ([84df364](https://github.com/k8sgpt-ai/k8sgpt/commit/84df3640bc114bb2c768f158d3575732103ff799))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.7 ([#753](https://github.com/k8sgpt-ai/k8sgpt/issues/753)) ([9971699](https://github.com/k8sgpt-ai/k8sgpt/commit/9971699fcf42b3309449d81875d45180f723de8d))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.8 ([#761](https://github.com/k8sgpt-ai/k8sgpt/issues/761)) ([beaa532](https://github.com/k8sgpt-ai/k8sgpt/commit/beaa53251c8201028db83d60f208e2b0658c93d8))\n* **deps:** update module google.golang.org/api to v0.151.0 ([#763](https://github.com/k8sgpt-ai/k8sgpt/issues/763)) ([3e3f6a9](https://github.com/k8sgpt-ai/k8sgpt/commit/3e3f6a903a81d9622660f5adf9cae7d22a5c99f4))\n* show trivy as active when activated with --no-install flag ([#675](https://github.com/k8sgpt-ai/k8sgpt/issues/675)) ([7368271](https://github.com/k8sgpt-ai/k8sgpt/commit/73682717eda4fa2e0cbc6311d5c97e01e0f2673c))\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.15.0 ([#765](https://github.com/k8sgpt-ai/k8sgpt/issues/765)) ([cf1e243](https://github.com/k8sgpt-ai/k8sgpt/commit/cf1e243708ab406f070da3f96be1fc60b7ce2ea4))\n* **deps:** update docker/build-push-action digest to 4a13e50 ([#760](https://github.com/k8sgpt-ai/k8sgpt/issues/760)) ([b5853de](https://github.com/k8sgpt-ai/k8sgpt/commit/b5853de8a6fcd17b1c1a4c53dbe3ffc82b83f72f))\n\n## [0.3.21](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.20...v0.3.21) (2023-11-12)\n\n\n### Features\n\n* auth remove: add -b flag ([#711](https://github.com/k8sgpt-ai/k8sgpt/issues/711)) ([9dadd18](https://github.com/k8sgpt-ai/k8sgpt/commit/9dadd186c8d03a4284faff3f0842d6e2d00ebbb8))\n* log analyzer ([#744](https://github.com/k8sgpt-ai/k8sgpt/issues/744)) ([d365886](https://github.com/k8sgpt-ai/k8sgpt/commit/d365886753f785bd58118c03510696318ea47941))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.6 ([#728](https://github.com/k8sgpt-ai/k8sgpt/issues/728)) ([bb21ce8](https://github.com/k8sgpt-ai/k8sgpt/commit/bb21ce80c782e011dfa1f808ccdd82ae748bfed8))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.8 ([#741](https://github.com/k8sgpt-ai/k8sgpt/issues/741)) ([d359caa](https://github.com/k8sgpt-ai/k8sgpt/commit/d359caaab6bdb42a54d305be2f4cd8452f512bb8))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.9 ([#743](https://github.com/k8sgpt-ai/k8sgpt/issues/743)) ([45ebad7](https://github.com/k8sgpt-ai/k8sgpt/commit/45ebad7b4d80d93920d5fbad9f42c8fcd45218bd))\n* **deps:** update module github.com/fatih/color to v1.16.0 ([#734](https://github.com/k8sgpt-ai/k8sgpt/issues/734)) ([8ab26d9](https://github.com/k8sgpt-ai/k8sgpt/commit/8ab26d96cec73369ecf014d50fccc26afe15fa44))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.3 ([#737](https://github.com/k8sgpt-ai/k8sgpt/issues/737)) ([48486e9](https://github.com/k8sgpt-ai/k8sgpt/commit/48486e96274a5e52a03cef00bd531148e27b38c5))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.17.5 ([#742](https://github.com/k8sgpt-ai/k8sgpt/issues/742)) ([3bff9cb](https://github.com/k8sgpt-ai/k8sgpt/commit/3bff9cbe7bb3afb7212735eb91902fd83d3cbb8c))\n* **deps:** update module github.com/schollz/progressbar/v3 to v3.14.1 ([#738](https://github.com/k8sgpt-ai/k8sgpt/issues/738)) ([05f444d](https://github.com/k8sgpt-ai/k8sgpt/commit/05f444dec1f234c191e25f71f3eab4838eb2477a))\n* **deps:** update module github.com/spf13/cobra to v1.8.0 ([#732](https://github.com/k8sgpt-ai/k8sgpt/issues/732)) ([19e502a](https://github.com/k8sgpt-ai/k8sgpt/commit/19e502a841e0463b682b0c6b8291f10aee616d7e))\n* **deps:** update module helm.sh/helm/v3 to v3.13.2 ([#740](https://github.com/k8sgpt-ai/k8sgpt/issues/740)) ([6a665f0](https://github.com/k8sgpt-ai/k8sgpt/commit/6a665f05d782ba9c3051df7a15ff304c89cb34f4))\n\n\n### Other\n\n* **deps:** pin google-github-actions/release-please-action action to db8f2c6 ([#747](https://github.com/k8sgpt-ai/k8sgpt/issues/747)) ([4408110](https://github.com/k8sgpt-ai/k8sgpt/commit/4408110b1a4835bb237b3d5674d6fa8a13f0181b))\n* **deps:** update google-github-actions/release-please-action digest to 4c5670f ([#721](https://github.com/k8sgpt-ai/k8sgpt/issues/721)) ([9c518ba](https://github.com/k8sgpt-ai/k8sgpt/commit/9c518badf53e4ccd9c2f9251cead4692602c0762))\n* **deps:** update google-github-actions/release-please-action digest to db8f2c6 ([#736](https://github.com/k8sgpt-ai/k8sgpt/issues/736)) ([fdb2934](https://github.com/k8sgpt-ai/k8sgpt/commit/fdb2934e8fd02bcb4e47b34c1eca5b099f462faa))\n* enable automerge for renovate ([#745](https://github.com/k8sgpt-ai/k8sgpt/issues/745)) ([66ebb88](https://github.com/k8sgpt-ai/k8sgpt/commit/66ebb88efe1ad5ecae75a5299f58a1e68179b515))\n* pin release-please version ([#746](https://github.com/k8sgpt-ai/k8sgpt/issues/746)) ([c4925b2](https://github.com/k8sgpt-ai/k8sgpt/commit/c4925b2170546d0d86b77d2a13c13d4907e2e3d6))\n\n\n### Dependency Updates\n\n* bump docker fixes CVE GHSA-jq35-85cj-fj4p ([#733](https://github.com/k8sgpt-ai/k8sgpt/issues/733)) ([120027e](https://github.com/k8sgpt-ai/k8sgpt/commit/120027e3cbec2535f0b6cc8d8db1dc27dd9f3ec6))\n\n## [0.3.20](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.19...v0.3.20) (2023-11-05)\n\n\n### Features\n\n* amazonsagemaker AI provider ([#731](https://github.com/k8sgpt-ai/k8sgpt/issues/731)) ([ccef7f6](https://github.com/k8sgpt-ai/k8sgpt/commit/ccef7f617004723b37d1e8ffb011398005e0b392))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.47.1 ([#724](https://github.com/k8sgpt-ai/k8sgpt/issues/724)) ([0136b8f](https://github.com/k8sgpt-ai/k8sgpt/commit/0136b8f543a7052e967e29691afe1aab8e5fae1b))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.2.0 ([#723](https://github.com/k8sgpt-ai/k8sgpt/issues/723)) ([16b229d](https://github.com/k8sgpt-ai/k8sgpt/commit/16b229d5478085655041ff0230d2542c4c0c7ce9))\n* **deps:** update module google.golang.org/grpc to v1.59.0 ([#713](https://github.com/k8sgpt-ai/k8sgpt/issues/713)) ([901c5ec](https://github.com/k8sgpt-ai/k8sgpt/commit/901c5ec18858f2f7fd385ff20aef77d203748c93))\n* **deps:** update module helm.sh/helm/v3 to v3.13.1 ([#706](https://github.com/k8sgpt-ai/k8sgpt/issues/706)) ([40133ad](https://github.com/k8sgpt-ai/k8sgpt/commit/40133adaedff3862199e00f62877a88fcffa67c5))\n* ensure ingress HTTP rule exists to prevent panic ([#726](https://github.com/k8sgpt-ai/k8sgpt/issues/726)) ([37721b5](https://github.com/k8sgpt-ai/k8sgpt/commit/37721b5dd77d66edfb7e8377b2b96470b8a21d1b))\n\n\n### Other\n\n* **deps:** update amannn/action-semantic-pull-request action to v5.4.0 ([#729](https://github.com/k8sgpt-ai/k8sgpt/issues/729)) ([188a8a2](https://github.com/k8sgpt-ai/k8sgpt/commit/188a8a2cd5e25b35446e2eab46279a0ba3976af3))\n\n## [0.3.19](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.18...v0.3.19) (2023-10-28)\n\n\n### Features\n\n* add amazonbedrock ([#718](https://github.com/k8sgpt-ai/k8sgpt/issues/718)) ([f1a7801](https://github.com/k8sgpt-ai/k8sgpt/commit/f1a7801e9e6a7e4a5310622951dfba3ba3acd047))\n* add Azure remote cache ([#690](https://github.com/k8sgpt-ai/k8sgpt/issues/690)) ([23ac52d](https://github.com/k8sgpt-ai/k8sgpt/commit/23ac52d5ffc0b2ebb7516b070fa740108cb4299a))\n\n\n### Bug Fixes\n\n* **deps:** update kubernetes packages to v0.28.3 ([#715](https://github.com/k8sgpt-ai/k8sgpt/issues/715)) ([7e73f8a](https://github.com/k8sgpt-ai/k8sgpt/commit/7e73f8afbce7ba0e9de432671b88c01fcfe28c3a))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.25 ([#707](https://github.com/k8sgpt-ai/k8sgpt/issues/707)) ([3ebc867](https://github.com/k8sgpt-ai/k8sgpt/commit/3ebc86772dc8f8cb2d2246724f5fd05d1e931512))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.26 ([#709](https://github.com/k8sgpt-ai/k8sgpt/issues/709)) ([c977528](https://github.com/k8sgpt-ai/k8sgpt/commit/c977528ec7839902570785e0803f6c6b83a0a69d))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.46.5 ([#712](https://github.com/k8sgpt-ai/k8sgpt/issues/712)) ([63a2260](https://github.com/k8sgpt-ai/k8sgpt/commit/63a226065c8068f9bdc0aa791a325fa10bba3fcc))\n* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.4.0 ([#722](https://github.com/k8sgpt-ai/k8sgpt/issues/722)) ([0e7219a](https://github.com/k8sgpt-ai/k8sgpt/commit/0e7219a36aaa718b7d86adf0a218a521bfac119b))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.16.0 ([#703](https://github.com/k8sgpt-ai/k8sgpt/issues/703)) ([b5facd6](https://github.com/k8sgpt-ai/k8sgpt/commit/b5facd64a340a96d38faf045bbb889b928ef08a1))\n* **deps:** update module github.com/spf13/viper to v1.17.0 ([#700](https://github.com/k8sgpt-ai/k8sgpt/issues/700)) ([184d148](https://github.com/k8sgpt-ai/k8sgpt/commit/184d1481081f4297bec21fbd60d7eff1964944ae))\n* **deps:** update module google.golang.org/grpc to v1.58.3 ([#704](https://github.com/k8sgpt-ai/k8sgpt/issues/704)) ([1d7360c](https://github.com/k8sgpt-ai/k8sgpt/commit/1d7360c0ae4dab376872acc71dc68d59eb4d9752))\n\n\n### Other\n\n* **deps:** update actions/checkout digest to b4ffde6 ([#719](https://github.com/k8sgpt-ai/k8sgpt/issues/719)) ([a77bd41](https://github.com/k8sgpt-ai/k8sgpt/commit/a77bd410489e624d29ccc8fd45a004f6844b3620))\n* **deps:** update module oras.land/oras-go to v1.2.4 ([#665](https://github.com/k8sgpt-ai/k8sgpt/issues/665)) ([4af0ad0](https://github.com/k8sgpt-ai/k8sgpt/commit/4af0ad0303d9b0ffb43f1e87fb5abe279d9a8724))\n\n## [0.3.18](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.17...v0.3.18) (2023-10-12)\n\n\n### Features\n\n* adding temperature to server mode ([#705](https://github.com/k8sgpt-ai/k8sgpt/issues/705)) ([539ca3b](https://github.com/k8sgpt-ai/k8sgpt/commit/539ca3b78f96694c11f788255d3b83d2fb335df4))\n\n\n### Bug Fixes\n\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20231002095256-194bc640518b.1 ([#692](https://github.com/k8sgpt-ai/k8sgpt/issues/692)) ([4d4e33b](https://github.com/k8sgpt-ai/k8sgpt/commit/4d4e33bea9cc4f5f9bf5379db5b890d9ba86e0a9))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go to v1.31.0-20231002095256-194bc640518b.1 ([#693](https://github.com/k8sgpt-ai/k8sgpt/issues/693)) ([20e6bd8](https://github.com/k8sgpt-ai/k8sgpt/commit/20e6bd816f636d4e4c8274d417870ec28fdd8a56))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.20 ([#685](https://github.com/k8sgpt-ai/k8sgpt/issues/685)) ([2494946](https://github.com/k8sgpt-ai/k8sgpt/commit/2494946dc867a532460bd6aac74dfb7da5184c1c))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.21 ([#696](https://github.com/k8sgpt-ai/k8sgpt/issues/696)) ([95c8cc0](https://github.com/k8sgpt-ai/k8sgpt/commit/95c8cc0afb0bb7b99784dcc5ba155f94b5a7dbdf))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.22 ([#697](https://github.com/k8sgpt-ai/k8sgpt/issues/697)) ([923a8c1](https://github.com/k8sgpt-ai/k8sgpt/commit/923a8c13c06b152d04e8b00ab002e2036bf12740))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.23 ([#699](https://github.com/k8sgpt-ai/k8sgpt/issues/699)) ([3f36a44](https://github.com/k8sgpt-ai/k8sgpt/commit/3f36a4441532e3d0ac1bd9d00fc738d4902b23a8))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.24 ([#701](https://github.com/k8sgpt-ai/k8sgpt/issues/701)) ([6d3038b](https://github.com/k8sgpt-ai/k8sgpt/commit/6d3038b0e8336235dc6a2fdb69d2381790331596))\n* **deps:** update module github.com/prometheus/client_golang to v1.17.0 ([#687](https://github.com/k8sgpt-ai/k8sgpt/issues/687)) ([9597002](https://github.com/k8sgpt-ai/k8sgpt/commit/95970027237e0079ed1f66dc9655fa01b181f4d7))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.15.4 ([#689](https://github.com/k8sgpt-ai/k8sgpt/issues/689)) ([f11d314](https://github.com/k8sgpt-ai/k8sgpt/commit/f11d3149b228b643155ed66c189cb0f8a4dd5a0f))\n* **deps:** update module helm.sh/helm/v3 to v3.13.0 ([#688](https://github.com/k8sgpt-ai/k8sgpt/issues/688)) ([87c8bce](https://github.com/k8sgpt-ai/k8sgpt/commit/87c8bcea4becd165aeb0ac98d79df7dab9c37ee3))\n* security warning around printing provider details in https://github.com/k8sgpt-ai/k8sgpt/security/code-scanning/1 ([#695](https://github.com/k8sgpt-ai/k8sgpt/issues/695)) ([85ce557](https://github.com/k8sgpt-ai/k8sgpt/commit/85ce55768199f90b1d2a5118ec2621ea5c7a7a67))\n\n\n### Other\n\n* **deps:** update amannn/action-semantic-pull-request action to v5.3.0 ([#683](https://github.com/k8sgpt-ai/k8sgpt/issues/683)) ([c5a8c46](https://github.com/k8sgpt-ai/k8sgpt/commit/c5a8c462989c097bf37ac48ea4f1a9010285042c))\n* fixing default model issue ([#702](https://github.com/k8sgpt-ai/k8sgpt/issues/702)) ([2a34ff2](https://github.com/k8sgpt-ai/k8sgpt/commit/2a34ff24d1f391270ae42531807cb1422880ad27))\n\n## [0.3.17](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.16...v0.3.17) (2023-09-28)\n\n\n### Features\n\n* added create namespace on deploy ([#673](https://github.com/k8sgpt-ai/k8sgpt/issues/673)) ([820e475](https://github.com/k8sgpt-ai/k8sgpt/commit/820e4755a54ecab3b5d800017bf6948dc9212825))\n* integration refactor ([#684](https://github.com/k8sgpt-ai/k8sgpt/issues/684)) ([69fe2db](https://github.com/k8sgpt-ai/k8sgpt/commit/69fe2db8acb795add27f04c1c8ee8d05819300ac))\n* update readme with new analyzers ([#671](https://github.com/k8sgpt-ai/k8sgpt/issues/671)) ([cad605a](https://github.com/k8sgpt-ai/k8sgpt/commit/cad605af462ce8b02ffc279ea847e41b7a64196f))\n\n\n### Bug Fixes\n\n* **deps:** update kubernetes packages to v0.28.2 ([#607](https://github.com/k8sgpt-ai/k8sgpt/issues/607)) ([ddeff9f](https://github.com/k8sgpt-ai/k8sgpt/commit/ddeff9fae4e80d1452893c59b89742633eb6b51b))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.14 ([#672](https://github.com/k8sgpt-ai/k8sgpt/issues/672)) ([1da4b7c](https://github.com/k8sgpt-ai/k8sgpt/commit/1da4b7c8f0eee877d5b76a7dd9abda7631d922f3))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.16 ([#682](https://github.com/k8sgpt-ai/k8sgpt/issues/682)) ([e1a42ff](https://github.com/k8sgpt-ai/k8sgpt/commit/e1a42ff3bcb3ddea71df2a5b5288eade024684dc))\n* **deps:** update module github.com/google/gnostic to v0.7.0 ([#679](https://github.com/k8sgpt-ai/k8sgpt/issues/679)) ([901ffb8](https://github.com/k8sgpt-ai/k8sgpt/commit/901ffb8df451ce41e6dc96da61deab987e51b6df))\n* **deps:** update module google.golang.org/grpc to v1.58.2 ([#680](https://github.com/k8sgpt-ai/k8sgpt/issues/680)) ([402e97d](https://github.com/k8sgpt-ai/k8sgpt/commit/402e97d05ea33879d997d98019b72da0f1074fc7))\n\n\n### Other\n\n* **deps:** update actions/checkout digest to 8ade135 ([#681](https://github.com/k8sgpt-ai/k8sgpt/issues/681)) ([aa9e6a3](https://github.com/k8sgpt-ai/k8sgpt/commit/aa9e6a3549877260423462c35ebbdfd95381be2c))\n\n## [0.3.16](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.15...v0.3.16) (2023-09-19)\n\n\n### Features\n\n* lists activate integrations ([#669](https://github.com/k8sgpt-ai/k8sgpt/issues/669)) ([844ff1f](https://github.com/k8sgpt-ai/k8sgpt/commit/844ff1fc78e7c35837c08b72bd2c19e92698d53d))\n* openAI explicit value for maxToken and temperature ([#659](https://github.com/k8sgpt-ai/k8sgpt/issues/659)) ([f55946d](https://github.com/k8sgpt-ai/k8sgpt/commit/f55946d60ebc7725aba6702570ca1cb5ba978d78))\n* serve/integration capability ([#645](https://github.com/k8sgpt-ai/k8sgpt/issues/645)) ([ab064b9](https://github.com/k8sgpt-ai/k8sgpt/commit/ab064b940cdb39a1588816221b20191e68263c61))\n\n\n### Bug Fixes\n\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20230830164712-dc062a152c20.1 ([#617](https://github.com/k8sgpt-ai/k8sgpt/issues/617)) ([d6b7b81](https://github.com/k8sgpt-ai/k8sgpt/commit/d6b7b818aef1b7775d1e76231077b74481546c56))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.10 ([#657](https://github.com/k8sgpt-ai/k8sgpt/issues/657)) ([0325724](https://github.com/k8sgpt-ai/k8sgpt/commit/03257246589ebbb22961e13394e49b52cb056e38))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.11 ([#662](https://github.com/k8sgpt-ai/k8sgpt/issues/662)) ([1b80b0c](https://github.com/k8sgpt-ai/k8sgpt/commit/1b80b0ce95f39c1cf27ad8bbb05a7fed10322114))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.12 ([#666](https://github.com/k8sgpt-ai/k8sgpt/issues/666)) ([b4656f5](https://github.com/k8sgpt-ai/k8sgpt/commit/b4656f533bdf39d12b223158bf41087076fa6c9a))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.15.3 ([#636](https://github.com/k8sgpt-ai/k8sgpt/issues/636)) ([54caff8](https://github.com/k8sgpt-ai/k8sgpt/commit/54caff837dc25ae594c6cd0e1bd0b31b1612cf73))\n* **deps:** update module go.uber.org/zap to v1.26.0 ([#658](https://github.com/k8sgpt-ai/k8sgpt/issues/658)) ([f76b572](https://github.com/k8sgpt-ai/k8sgpt/commit/f76b57265432a704c3fc5aa67b0d569179b4ef03))\n* **deps:** update module google.golang.org/grpc to v1.58.0 ([#635](https://github.com/k8sgpt-ai/k8sgpt/issues/635)) ([d58e002](https://github.com/k8sgpt-ai/k8sgpt/commit/d58e002d7dc55cc759402fcadb03af921cd30dc3))\n* **deps:** update module google.golang.org/grpc to v1.58.1 ([#656](https://github.com/k8sgpt-ai/k8sgpt/issues/656)) ([abfb584](https://github.com/k8sgpt-ai/k8sgpt/commit/abfb58432fbd1168db13880e5b9dbcbdde70f147))\n* emergency fix for bad package revision in go mod ([#663](https://github.com/k8sgpt-ai/k8sgpt/issues/663)) ([2472da1](https://github.com/k8sgpt-ai/k8sgpt/commit/2472da167300a831dc5b45f7fc0169a0b5b1ccb7))\n* pdb panic error guard ([#664](https://github.com/k8sgpt-ai/k8sgpt/issues/664)) ([3277b2a](https://github.com/k8sgpt-ai/k8sgpt/commit/3277b2ad4b27ade9bd7da07f5fc8d8f074355177))\n* respect namespace scope in trivy analyzer ([#661](https://github.com/k8sgpt-ai/k8sgpt/issues/661)) ([6481590](https://github.com/k8sgpt-ai/k8sgpt/commit/6481590b29b80391ea1c9298cae5d8f0a4ae7354))\n* use default values when adding auth ([#568](https://github.com/k8sgpt-ai/k8sgpt/issues/568)) ([7461a74](https://github.com/k8sgpt-ai/k8sgpt/commit/7461a748f8e994e58ac4f56fd9919b1744bd7366)), closes [#567](https://github.com/k8sgpt-ai/k8sgpt/issues/567)\n\n\n### Other\n\n* **deps:** update actions/upload-artifact digest to a8a3f3a ([#633](https://github.com/k8sgpt-ai/k8sgpt/issues/633)) ([4bfc7f9](https://github.com/k8sgpt-ai/k8sgpt/commit/4bfc7f996c851adadc5ab0754da6852979084e9d))\n* **deps:** update reviewdog/action-golangci-lint digest to 24d4af2 ([#642](https://github.com/k8sgpt-ai/k8sgpt/issues/642)) ([f607360](https://github.com/k8sgpt-ai/k8sgpt/commit/f60736035b2601650f4b3ee352f16d1e57d6ec64))\n\n## [0.3.15](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.14...v0.3.15) (2023-09-14)\n\n\n### Features\n\n* show each ConfigAuditReport check ([#646](https://github.com/k8sgpt-ai/k8sgpt/issues/646)) ([230eace](https://github.com/k8sgpt-ai/k8sgpt/commit/230eace18737a81e4c023826ffef1a9b1e17d4fd))\n\n\n### Bug Fixes\n\n* defer to service analyser when selectors are missing ([#652](https://github.com/k8sgpt-ai/k8sgpt/issues/652)) ([6c5a062](https://github.com/k8sgpt-ai/k8sgpt/commit/6c5a0628e4a8c493beae85049448e6e6588d63be))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.333 ([#611](https://github.com/k8sgpt-ai/k8sgpt/issues/611)) ([96d97cf](https://github.com/k8sgpt-ai/k8sgpt/commit/96d97cfa30c4d3c75facda3d3016c080dfa86eaa))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.0 ([#618](https://github.com/k8sgpt-ai/k8sgpt/issues/618)) ([632fc9a](https://github.com/k8sgpt-ai/k8sgpt/commit/632fc9a99fd0482dcff0768211c49bffb2e4032a))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.1 ([#624](https://github.com/k8sgpt-ai/k8sgpt/issues/624)) ([09984c2](https://github.com/k8sgpt-ai/k8sgpt/commit/09984c245de40fc7794f85a9535af4f8e5f5e776))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.2 ([#625](https://github.com/k8sgpt-ai/k8sgpt/issues/625)) ([b6498ef](https://github.com/k8sgpt-ai/k8sgpt/commit/b6498ef269919c61004dd860ebf08ed7f28810f7))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.3 ([#632](https://github.com/k8sgpt-ai/k8sgpt/issues/632)) ([5f73240](https://github.com/k8sgpt-ai/k8sgpt/commit/5f73240a0615e58a37e9eb00784628621bc1dfa1))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.6 ([#634](https://github.com/k8sgpt-ai/k8sgpt/issues/634)) ([3aabb48](https://github.com/k8sgpt-ai/k8sgpt/commit/3aabb4842d96ec14e61842847dc2feb3e3f31a0a))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.45.9 ([#640](https://github.com/k8sgpt-ai/k8sgpt/issues/640)) ([95787f2](https://github.com/k8sgpt-ai/k8sgpt/commit/95787f2854c4e4a971b2d687d97a5ceca30b9d5e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.15.1 ([#622](https://github.com/k8sgpt-ai/k8sgpt/issues/622)) ([fc90dc8](https://github.com/k8sgpt-ai/k8sgpt/commit/fc90dc865b48fae99253b8bb6a8b1ae7047170b4))\n* **deps:** update module golang.org/x/term to v0.12.0 ([#626](https://github.com/k8sgpt-ai/k8sgpt/issues/626)) ([44d17c5](https://github.com/k8sgpt-ai/k8sgpt/commit/44d17c51ff8ece92cd0c85f40d15caa97d990544))\n* typos ([#629](https://github.com/k8sgpt-ai/k8sgpt/issues/629)) ([067c348](https://github.com/k8sgpt-ai/k8sgpt/commit/067c3483e6b379bd710c7f799de63bc1890b6c81))\n* use-case while in cluster, connecting to an external ([#623](https://github.com/k8sgpt-ai/k8sgpt/issues/623)) ([1a7f45c](https://github.com/k8sgpt-ai/k8sgpt/commit/1a7f45cc55348d567148d01e61c7527e4d534f34))\n\n\n### Other\n\n* **deps:** bump github.com/cyphar/filepath-securejoin ([#644](https://github.com/k8sgpt-ai/k8sgpt/issues/644)) ([25890e6](https://github.com/k8sgpt-ai/k8sgpt/commit/25890e6e3807171e655fec0d2081cedad3ad6273))\n* **deps:** update actions/checkout action to v4 ([#628](https://github.com/k8sgpt-ai/k8sgpt/issues/628)) ([e65d9a6](https://github.com/k8sgpt-ai/k8sgpt/commit/e65d9a650522120d602b2a62703aa2b39abfdea1))\n* **deps:** update actions/checkout digest to f43a0e5 ([#612](https://github.com/k8sgpt-ai/k8sgpt/issues/612)) ([6f9f7b2](https://github.com/k8sgpt-ai/k8sgpt/commit/6f9f7b2b602605f3be7fd02bd521574e9c26fa78))\n* **deps:** update docker/build-push-action action to v5 ([#643](https://github.com/k8sgpt-ai/k8sgpt/issues/643)) ([241f1bd](https://github.com/k8sgpt-ai/k8sgpt/commit/241f1bd6dfcb772711551aac42e48a2f59e64046))\n* **deps:** update docker/login-action action to v3 ([#648](https://github.com/k8sgpt-ai/k8sgpt/issues/648)) ([b491c92](https://github.com/k8sgpt-ai/k8sgpt/commit/b491c9200e781284737dd74a9789dfc0c1e7b14a))\n* **deps:** update docker/setup-buildx-action action to v3 ([#649](https://github.com/k8sgpt-ai/k8sgpt/issues/649)) ([598ef22](https://github.com/k8sgpt-ai/k8sgpt/commit/598ef22e570c1db678d583638c83e242f3b313d7))\n* **deps:** update docker/setup-buildx-action digest to 885d146 ([#615](https://github.com/k8sgpt-ai/k8sgpt/issues/615)) ([2c81dad](https://github.com/k8sgpt-ai/k8sgpt/commit/2c81dadb4d4abcdc3608be768c1f3aae87e53a68))\n* **deps:** update goreleaser/goreleaser-action action to v5 ([#641](https://github.com/k8sgpt-ai/k8sgpt/issues/641)) ([00d7a27](https://github.com/k8sgpt-ai/k8sgpt/commit/00d7a27ec1ea1bd49ab1879b8ffa0b9e7c0b6adf))\n* **deps:** update goreleaser/goreleaser-action digest to 5fdedb9 ([#631](https://github.com/k8sgpt-ai/k8sgpt/issues/631)) ([5de3b64](https://github.com/k8sgpt-ai/k8sgpt/commit/5de3b640988783df5a04db368f79b9b9eefdb8bf))\n* fixes a bug where filters do not deactive ([#621](https://github.com/k8sgpt-ai/k8sgpt/issues/621)) ([133850f](https://github.com/k8sgpt-ai/k8sgpt/commit/133850f984cc0bb41ec1e4521a32ab30558778f1))\n* slice loop replace ([#627](https://github.com/k8sgpt-ai/k8sgpt/issues/627)) ([c24825b](https://github.com/k8sgpt-ai/k8sgpt/commit/c24825b81025c5cd79224a79b52d6c5efdc00511))\n* updated protobuf libs ([#614](https://github.com/k8sgpt-ai/k8sgpt/issues/614)) ([5e17e66](https://github.com/k8sgpt-ai/k8sgpt/commit/5e17e666659c0eb057562def70d491daa995e5a2))\n* updated schema for integrations support ([#616](https://github.com/k8sgpt-ai/k8sgpt/issues/616)) ([8f0a2fd](https://github.com/k8sgpt-ai/k8sgpt/commit/8f0a2fd41d6705da4d1a1d288f3b6ce19711f30d))\n\n## [0.3.14](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.13...v0.3.14) (2023-08-25)\n\n\n### Features\n\n* configauditreport ([#609](https://github.com/k8sgpt-ai/k8sgpt/issues/609)) ([44d3613](https://github.com/k8sgpt-ai/k8sgpt/commit/44d3613c1f950837c6b112ddde0dc3e90f73dc1b))\n\n\n### Bug Fixes\n\n* **deps:** update kubernetes packages to v0.27.4 ([#565](https://github.com/k8sgpt-ai/k8sgpt/issues/565)) ([3cc7aa5](https://github.com/k8sgpt-ai/k8sgpt/commit/3cc7aa56d8efc6e78badf3be1cb3d5726074156e))\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.15.1 ([#576](https://github.com/k8sgpt-ai/k8sgpt/issues/576)) ([c364074](https://github.com/k8sgpt-ai/k8sgpt/commit/c3640744c5cbf036321a14b90c1fdefa17c5321d))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.304 ([#558](https://github.com/k8sgpt-ai/k8sgpt/issues/558)) ([cf9069e](https://github.com/k8sgpt-ai/k8sgpt/commit/cf9069ef572fea9a947d7de5b0c0e44f34620a69))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.307 ([#574](https://github.com/k8sgpt-ai/k8sgpt/issues/574)) ([8ae91ec](https://github.com/k8sgpt-ai/k8sgpt/commit/8ae91ec744d1fead3b0aa570c904e9e3ad5ab5ef))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.308 ([#579](https://github.com/k8sgpt-ai/k8sgpt/issues/579)) ([7e8668a](https://github.com/k8sgpt-ai/k8sgpt/commit/7e8668a56bb25b7da3957cf4c05847d022825c10))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.309 ([#584](https://github.com/k8sgpt-ai/k8sgpt/issues/584)) ([227e1cd](https://github.com/k8sgpt-ai/k8sgpt/commit/227e1cd69f38654126750902a89408643bdb30fb))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.312 ([#586](https://github.com/k8sgpt-ai/k8sgpt/issues/586)) ([aafac93](https://github.com/k8sgpt-ai/k8sgpt/commit/aafac9345fbab16b1fe23ea76d6c1c362c44c080))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.313 ([#587](https://github.com/k8sgpt-ai/k8sgpt/issues/587)) ([f1479ba](https://github.com/k8sgpt-ai/k8sgpt/commit/f1479babbaaf6770d4a106d80f22b2ffb736cbad))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.315 ([#588](https://github.com/k8sgpt-ai/k8sgpt/issues/588)) ([fe29361](https://github.com/k8sgpt-ai/k8sgpt/commit/fe29361e335f3d186dc3d7651823e9bb03649652))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.317 ([#591](https://github.com/k8sgpt-ai/k8sgpt/issues/591)) ([9802e82](https://github.com/k8sgpt-ai/k8sgpt/commit/9802e82ff54bc55b670e25f75c69a29a985c21ae))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.327 ([#597](https://github.com/k8sgpt-ai/k8sgpt/issues/597)) ([aee83b7](https://github.com/k8sgpt-ai/k8sgpt/commit/aee83b74b20117f136876ec426318914aee8c4d1))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.329 ([#610](https://github.com/k8sgpt-ai/k8sgpt/issues/610)) ([0e5be89](https://github.com/k8sgpt-ai/k8sgpt/commit/0e5be89e5ccb70e9e9a44ad70f161c7b344d04f2))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.3 ([#582](https://github.com/k8sgpt-ai/k8sgpt/issues/582)) ([c2770f3](https://github.com/k8sgpt-ai/k8sgpt/commit/c2770f38a6f0d3248747927155505db505f5e960))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.14.1 ([#573](https://github.com/k8sgpt-ai/k8sgpt/issues/573)) ([b52424a](https://github.com/k8sgpt-ai/k8sgpt/commit/b52424a9b1a554739cb8e08e296045c181d4041c))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.14.2 ([#603](https://github.com/k8sgpt-ai/k8sgpt/issues/603)) ([81fcf8b](https://github.com/k8sgpt-ai/k8sgpt/commit/81fcf8b5d46387eca7128b877c0652fdf4ed999c))\n* **deps:** update module go.uber.org/zap to v1.25.0 ([#589](https://github.com/k8sgpt-ai/k8sgpt/issues/589)) ([9672cea](https://github.com/k8sgpt-ai/k8sgpt/commit/9672cea228de976772f453e6a05ce05456741de8))\n* **deps:** update module golang.org/x/term to v0.11.0 ([#593](https://github.com/k8sgpt-ai/k8sgpt/issues/593)) ([7f109cd](https://github.com/k8sgpt-ai/k8sgpt/commit/7f109cdcfac00a329a53121287e44c2567af6b4a))\n* **deps:** update module google.golang.org/grpc to v1.57.0 ([#585](https://github.com/k8sgpt-ai/k8sgpt/issues/585)) ([59897f3](https://github.com/k8sgpt-ai/k8sgpt/commit/59897f330a037f1e5de0f958dd93b826e2ce481d))\n* **deps:** update module helm.sh/helm/v3 to v3.12.3 ([#602](https://github.com/k8sgpt-ai/k8sgpt/issues/602)) ([7910c9a](https://github.com/k8sgpt-ai/k8sgpt/commit/7910c9aa2c40f3c1837cce179dd1fc91a9744946))\n* optimize analyze service ([#461](https://github.com/k8sgpt-ai/k8sgpt/issues/461)) ([cc665ea](https://github.com/k8sgpt-ai/k8sgpt/commit/cc665ea4f3f279c30c7dd7996786e6bdce88acc8))\n* use kubeconfig file when user specify it ([#605](https://github.com/k8sgpt-ai/k8sgpt/issues/605)) ([e3b21ec](https://github.com/k8sgpt-ai/k8sgpt/commit/e3b21ec5ecd5f823470c2c2f570ed89a2c071b5a)), closes [#604](https://github.com/k8sgpt-ai/k8sgpt/issues/604)\n\n\n### Other\n\n* **deps:** exclude retracted cohere-go versions ([#583](https://github.com/k8sgpt-ai/k8sgpt/issues/583)) ([f8a53a5](https://github.com/k8sgpt-ai/k8sgpt/commit/f8a53a5c035fd3e3598666d9792c4e1231f9838d))\n* **deps:** update actions/setup-go digest to 93397be ([#600](https://github.com/k8sgpt-ai/k8sgpt/issues/600)) ([1a0ae1a](https://github.com/k8sgpt-ai/k8sgpt/commit/1a0ae1a086d328b1eaa70c412122427a6e8df2f5))\n* **deps:** update google-github-actions/release-please-action digest to ca6063f ([#572](https://github.com/k8sgpt-ai/k8sgpt/issues/572)) ([fba1a8e](https://github.com/k8sgpt-ai/k8sgpt/commit/fba1a8ed8c7cc2f7b0aace246f8797ea6c27e455))\n* **deps:** update goreleaser/goreleaser-action digest to 3fa32b8 ([#601](https://github.com/k8sgpt-ai/k8sgpt/issues/601)) ([610720a](https://github.com/k8sgpt-ai/k8sgpt/commit/610720a95c9d5eb49c77e7a929cd766a04e534a4))\n* **deps:** update reviewdog/action-golangci-lint digest to 951dc8b ([#594](https://github.com/k8sgpt-ai/k8sgpt/issues/594)) ([9acaec0](https://github.com/k8sgpt-ai/k8sgpt/commit/9acaec00c4d084c4ec3e40e4a6a8b0136dcc4aa1))\n* **deps:** update reviewdog/action-golangci-lint digest to f17c2e2 ([#598](https://github.com/k8sgpt-ai/k8sgpt/issues/598)) ([2251321](https://github.com/k8sgpt-ai/k8sgpt/commit/22513216960f06d572ec53480e290b1f4e5ff1d8))\n* upgraded cohere backend ([#580](https://github.com/k8sgpt-ai/k8sgpt/issues/580)) ([43b0d70](https://github.com/k8sgpt-ai/k8sgpt/commit/43b0d707e7eac326594f5f6c7ab4c885772846d2))\n\n## [0.3.13](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.12...v0.3.13) (2023-07-20)\n\n\n### Other\n\n* continue on absent service ([#569](https://github.com/k8sgpt-ai/k8sgpt/issues/569)) ([153d38d](https://github.com/k8sgpt-ai/k8sgpt/commit/153d38deb060cb84d606f8391e5700025ce02a9b))\n\n## [0.3.12](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.11...v0.3.12) (2023-07-19)\n\n\n### Features\n\n* add Cohere backend ([#563](https://github.com/k8sgpt-ai/k8sgpt/issues/563)) ([781ecb7](https://github.com/k8sgpt-ai/k8sgpt/commit/781ecb7aad689e6709678c9690c112115e3cf6c7))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.300 ([#554](https://github.com/k8sgpt-ai/k8sgpt/issues/554)) ([dc46333](https://github.com/k8sgpt-ai/k8sgpt/commit/dc463334bccdf16106cff4e688a83bf0984d6e27))\n* **deps:** update module github.com/mittwald/go-helm-client to v0.12.2 ([#562](https://github.com/k8sgpt-ai/k8sgpt/issues/562)) ([2e0db55](https://github.com/k8sgpt-ai/k8sgpt/commit/2e0db553f92b5ca691b5957b180be35131ab4e2f))\n* **deps:** update module google.golang.org/grpc to v1.56.2 ([#546](https://github.com/k8sgpt-ai/k8sgpt/issues/546)) ([cc83fe1](https://github.com/k8sgpt-ai/k8sgpt/commit/cc83fe19bafc87647fa0293189f90c84d2dd8edb))\n* **deps:** update module helm.sh/helm/v3 to v3.12.2 ([#555](https://github.com/k8sgpt-ai/k8sgpt/issues/555)) ([9eb96c4](https://github.com/k8sgpt-ai/k8sgpt/commit/9eb96c495cdb1247b664de625a036902b5e156ff))\n\n\n### Other\n\n* fixing edge cases with missing wh service ([#561](https://github.com/k8sgpt-ai/k8sgpt/issues/561)) ([c422215](https://github.com/k8sgpt-ai/k8sgpt/commit/c42221512bfdab7ac792963d459bf9f8dac3954c))\n\n\n### Docs\n\n* fix readme for anonymization ([#559](https://github.com/k8sgpt-ai/k8sgpt/issues/559)) ([70bec05](https://github.com/k8sgpt-ai/k8sgpt/commit/70bec050d854be6f559065278d6a583d8e0e333b))\n\n## [0.3.11](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.10...v0.3.11) (2023-07-14)\n\n\n### Features\n\n* admission webhooks ([#553](https://github.com/k8sgpt-ai/k8sgpt/issues/553)) ([06e8532](https://github.com/k8sgpt-ai/k8sgpt/commit/06e8532f88616a988a4e41ed8cdac62cf0f243a5))\n\n\n### Other\n\n* **deps:** update docker/setup-buildx-action digest to 4c0219f ([#547](https://github.com/k8sgpt-ai/k8sgpt/issues/547)) ([1a3f299](https://github.com/k8sgpt-ai/k8sgpt/commit/1a3f2992108e857f8c8c07eff16599d00b50110e))\n\n## [0.3.10](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.9...v0.3.10) (2023-07-12)\n\n\n### Features\n\n* add Validating/Mutating webhook analyzer ([#548](https://github.com/k8sgpt-ai/k8sgpt/issues/548)) ([750a10d](https://github.com/k8sgpt-ai/k8sgpt/commit/750a10d44c59bc90de5241d1128ee74fa38bf350))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.298 ([#545](https://github.com/k8sgpt-ai/k8sgpt/issues/545)) ([d1096dc](https://github.com/k8sgpt-ai/k8sgpt/commit/d1096dc31a692013f40980649e5cc2d402869ceb))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.299 ([#549](https://github.com/k8sgpt-ai/k8sgpt/issues/549)) ([ecd7790](https://github.com/k8sgpt-ai/k8sgpt/commit/ecd7790efe2ca88259451761202c90cb842ff04b))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.13.0 ([#399](https://github.com/k8sgpt-ai/k8sgpt/issues/399)) ([21df094](https://github.com/k8sgpt-ai/k8sgpt/commit/21df094bda31a14235fb2244e8cef74d3c92d919))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.14.0 ([#550](https://github.com/k8sgpt-ai/k8sgpt/issues/550)) ([9dcab94](https://github.com/k8sgpt-ai/k8sgpt/commit/9dcab945460e5972f895fa5302e3425750d635c7))\n* **deps:** update module golang.org/x/term to v0.10.0 ([#542](https://github.com/k8sgpt-ai/k8sgpt/issues/542)) ([1276b3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1276b3e89715b1cfb553e60d4f25592acef80a6f))\n\n## [0.3.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.8...v0.3.9) (2023-07-04)\n\n\n### Features\n\n* details flag to list command ([#537](https://github.com/k8sgpt-ai/k8sgpt/issues/537)) ([2309b0d](https://github.com/k8sgpt-ai/k8sgpt/commit/2309b0dfe20e27b6afe283a6be21ad7a0652ac99))\n* upgrading azure client impl ([#526](https://github.com/k8sgpt-ai/k8sgpt/issues/526)) ([367fe8f](https://github.com/k8sgpt-ai/k8sgpt/commit/367fe8f74c6a9e26f0d9c3b25a86093530fb85b2))\n* upgrading the proto files to fix user issues ([#515](https://github.com/k8sgpt-ai/k8sgpt/issues/515)) ([c88fc88](https://github.com/k8sgpt-ai/k8sgpt/commit/c88fc889e4f6089e48f37d90e349d5c61ea0b952))\n\n\n### Bug Fixes\n\n* 'intergration' typos ([#508](https://github.com/k8sgpt-ai/k8sgpt/issues/508)) ([64b93c9](https://github.com/k8sgpt-ai/k8sgpt/commit/64b93c9116b6a7f82419f1c4fff98fa68b8c0aca))\n* add --no-install for activate command ([#536](https://github.com/k8sgpt-ai/k8sgpt/issues/536)) ([1f5462c](https://github.com/k8sgpt-ai/k8sgpt/commit/1f5462c80bd04f63c2b55889c987634251635812))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20230620082254-6f80f9533908.1 ([#516](https://github.com/k8sgpt-ai/k8sgpt/issues/516)) ([06e50d5](https://github.com/k8sgpt-ai/k8sgpt/commit/06e50d57db3aa2e5a68b093e2ba25c0e33dc6343))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.284 ([#501](https://github.com/k8sgpt-ai/k8sgpt/issues/501)) ([d87127a](https://github.com/k8sgpt-ai/k8sgpt/commit/d87127a309734847a56bf95c2e947e2270f94a88))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.286 ([#514](https://github.com/k8sgpt-ai/k8sgpt/issues/514)) ([b9cf522](https://github.com/k8sgpt-ai/k8sgpt/commit/b9cf5226853619655e98f2156bfd0b8513511bb3))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.288 ([#519](https://github.com/k8sgpt-ai/k8sgpt/issues/519)) ([57695b4](https://github.com/k8sgpt-ai/k8sgpt/commit/57695b44b6429319860a76e4e02016dafe3ed0b0))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.289 ([#524](https://github.com/k8sgpt-ai/k8sgpt/issues/524)) ([fafb695](https://github.com/k8sgpt-ai/k8sgpt/commit/fafb69544f4edda670bad6973332a20a7f0f055e))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.292 ([#530](https://github.com/k8sgpt-ai/k8sgpt/issues/530)) ([566f752](https://github.com/k8sgpt-ai/k8sgpt/commit/566f7525eef9f65dd2ab6a47bd0012bfb91e2a12))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.294 ([#535](https://github.com/k8sgpt-ai/k8sgpt/issues/535)) ([3067fa9](https://github.com/k8sgpt-ai/k8sgpt/commit/3067fa98f4a8990c9a930e53ad93f89cf35e0d62))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.295 ([#540](https://github.com/k8sgpt-ai/k8sgpt/issues/540)) ([767e4cb](https://github.com/k8sgpt-ai/k8sgpt/commit/767e4cbc4127e2017a3a9c4b182ccc833debc6a5))\n* **deps:** update module github.com/prometheus/client_golang to v1.16.0 ([#507](https://github.com/k8sgpt-ai/k8sgpt/issues/507)) ([14e5691](https://github.com/k8sgpt-ai/k8sgpt/commit/14e5691190bf772c05477cbcb811ed71bec450a3))\n* **deps:** update module google.golang.org/grpc to v1.56.0 ([#510](https://github.com/k8sgpt-ai/k8sgpt/issues/510)) ([f3e0b9b](https://github.com/k8sgpt-ai/k8sgpt/commit/f3e0b9b56d13397c79f57e76bdd6b741bb565fb4))\n* **deps:** update module google.golang.org/grpc to v1.56.1 ([#520](https://github.com/k8sgpt-ai/k8sgpt/issues/520)) ([be52308](https://github.com/k8sgpt-ai/k8sgpt/commit/be52308c99f6aed73e2c20d260823795d45876f5))\n* **deps:** update module helm.sh/helm/v3 to v3.12.1 ([#503](https://github.com/k8sgpt-ai/k8sgpt/issues/503)) ([0f03ddc](https://github.com/k8sgpt-ai/k8sgpt/commit/0f03ddcf0f5ec79bc6dbb74c654e0d8fac634a0a))\n* displaying correct yaml config location on app start ([#521](https://github.com/k8sgpt-ai/k8sgpt/issues/521)) ([b7d4602](https://github.com/k8sgpt-ai/k8sgpt/commit/b7d4602cb8aaaa0c22a0a5941d8c6edad7c58db4))\n* remove provider from default on delete ([#529](https://github.com/k8sgpt-ai/k8sgpt/issues/529)) ([5a983c4](https://github.com/k8sgpt-ai/k8sgpt/commit/5a983c4a0a511389e25cffe12999b903b85cd96d))\n* typo in add command ([#539](https://github.com/k8sgpt-ai/k8sgpt/issues/539)) ([da750df](https://github.com/k8sgpt-ai/k8sgpt/commit/da750df16bde878f7619aa58ef5e7ef7d3173b2b))\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.14.3 ([#517](https://github.com/k8sgpt-ai/k8sgpt/issues/517)) ([0521060](https://github.com/k8sgpt-ai/k8sgpt/commit/05210604109a6e892bb465df11038b8c24d68076))\n* **deps:** update docker/build-push-action digest to 2eb1c19 ([#499](https://github.com/k8sgpt-ai/k8sgpt/issues/499)) ([9df75cc](https://github.com/k8sgpt-ai/k8sgpt/commit/9df75cc959f7ed23cae8e3761498ea6c56885788))\n* **deps:** update docker/login-action digest to 465a078 ([#488](https://github.com/k8sgpt-ai/k8sgpt/issues/488)) ([c15a561](https://github.com/k8sgpt-ai/k8sgpt/commit/c15a561b635dc678bb8de15c6623914942475537))\n* **deps:** update docker/setup-buildx-action digest to 16c0bc4 ([#532](https://github.com/k8sgpt-ai/k8sgpt/issues/532)) ([5662d59](https://github.com/k8sgpt-ai/k8sgpt/commit/5662d5932ff3beb8c1a31fc2088c5e703e90ec79))\n* **deps:** update docker/setup-buildx-action digest to ecf9528 ([#498](https://github.com/k8sgpt-ai/k8sgpt/issues/498)) ([f4d7876](https://github.com/k8sgpt-ai/k8sgpt/commit/f4d78768388774f62d87acd89e71689535e538f7))\n* **deps:** update google-github-actions/release-please-action digest to 8016a66 ([#523](https://github.com/k8sgpt-ai/k8sgpt/issues/523)) ([d56861d](https://github.com/k8sgpt-ai/k8sgpt/commit/d56861d4bad475da09992813fc256a0d99399eab))\n* **deps:** update reviewdog/action-golangci-lint digest to 22adb9d ([#525](https://github.com/k8sgpt-ai/k8sgpt/issues/525)) ([3146754](https://github.com/k8sgpt-ai/k8sgpt/commit/314675477917063dcfb847880fb3186f8bdf32f6))\n* **deps:** update reviewdog/action-golangci-lint digest to 994abff ([#513](https://github.com/k8sgpt-ai/k8sgpt/issues/513)) ([1819c3b](https://github.com/k8sgpt-ai/k8sgpt/commit/1819c3bf1512291cd637c115e8b82c9e0e8885a0))\n* sorting out the dependency hell ([#518](https://github.com/k8sgpt-ai/k8sgpt/issues/518)) ([cd7807a](https://github.com/k8sgpt-ai/k8sgpt/commit/cd7807a48481f298422d9b1c8066b431fad3ae5a))\n\n## [0.3.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.7...v0.3.8) (2023-06-15)\n\n\n### Features\n\n* fix for s3 cache from operator ([f6db6ce](https://github.com/k8sgpt-ai/k8sgpt/commit/f6db6ce86163dcb4b5ec4bd99b8a3842dd0c60bb))\n\n\n### Bug Fixes\n\n* **deps:** update kubernetes packages to v0.27.3 ([#504](https://github.com/k8sgpt-ai/k8sgpt/issues/504)) ([b1c6ec3](https://github.com/k8sgpt-ai/k8sgpt/commit/b1c6ec3c0919649bc551ec0047a1d9c2420f4264))\n\n## [0.3.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.6...v0.3.7) (2023-06-13)\n\n\n### Features\n\n* add update to auth cmd ([#450](https://github.com/k8sgpt-ai/k8sgpt/issues/450)) ([01aeeb3](https://github.com/k8sgpt-ai/k8sgpt/commit/01aeeb35e2dab957d2909cd5cffc5a4a03e19664))\n* support arbitrary uid for openshift environments ([#454](https://github.com/k8sgpt-ai/k8sgpt/issues/454)) ([92539ee](https://github.com/k8sgpt-ai/k8sgpt/commit/92539ee05d2d15e951742aaaf07f2defff3f79c5))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.274 ([#474](https://github.com/k8sgpt-ai/k8sgpt/issues/474)) ([1a81227](https://github.com/k8sgpt-ai/k8sgpt/commit/1a81227d6148be59b7b9ae4e9ae5e2d9a5b7a9ae))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.275 ([#478](https://github.com/k8sgpt-ai/k8sgpt/issues/478)) ([705b54f](https://github.com/k8sgpt-ai/k8sgpt/commit/705b54fcd308ef1fc0bc870b5a0a32baa30767df))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.276 ([#482](https://github.com/k8sgpt-ai/k8sgpt/issues/482)) ([3f0aea1](https://github.com/k8sgpt-ai/k8sgpt/commit/3f0aea131e1e62655a10f6a51bf6238316dd6598))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.277 ([#485](https://github.com/k8sgpt-ai/k8sgpt/issues/485)) ([e2d5c2d](https://github.com/k8sgpt-ai/k8sgpt/commit/e2d5c2dee00e3411fa10bcaa4ae134b5671f45ab))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.280 ([#490](https://github.com/k8sgpt-ai/k8sgpt/issues/490)) ([04b4f56](https://github.com/k8sgpt-ai/k8sgpt/commit/04b4f56a667febf77c21838a618a1cd4a7f1e371))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.281 ([#496](https://github.com/k8sgpt-ai/k8sgpt/issues/496)) ([a6342c9](https://github.com/k8sgpt-ai/k8sgpt/commit/a6342c92830451e3110a54bac80a1b693984fcc8))\n* **deps:** update module golang.org/x/term to v0.9.0 ([#497](https://github.com/k8sgpt-ai/k8sgpt/issues/497)) ([98b852a](https://github.com/k8sgpt-ai/k8sgpt/commit/98b852aabe1ff62ac64e3c9e3e70173a8ff19749))\n* use the `status` for pdb checking ([#477](https://github.com/k8sgpt-ai/k8sgpt/issues/477)) ([075066d](https://github.com/k8sgpt-ai/k8sgpt/commit/075066dd7c353c0afd36637f421229cba5a6e022)), closes [#476](https://github.com/k8sgpt-ai/k8sgpt/issues/476)\n\n\n### Docs\n\n* fix add localai command in readme.md ([#494](https://github.com/k8sgpt-ai/k8sgpt/issues/494)) ([40fbba7](https://github.com/k8sgpt-ai/k8sgpt/commit/40fbba7df1b2ce40c99262c901c7d2a26e9bbed0))\n\n\n### Other\n\n* customized prompt template for integration plugins ([#403](https://github.com/k8sgpt-ai/k8sgpt/issues/403)) ([c85203b](https://github.com/k8sgpt-ai/k8sgpt/commit/c85203bccde094c33ef83eb728aeed2608cbc136))\n* **deps:** update actions/checkout digest to c85c95e ([#492](https://github.com/k8sgpt-ai/k8sgpt/issues/492)) ([1ae21e6](https://github.com/k8sgpt-ai/k8sgpt/commit/1ae21e6fd46b8490ea012fa8176d741af2e71e7e))\n* **deps:** update docker/build-push-action digest to 44ea916 ([#491](https://github.com/k8sgpt-ai/k8sgpt/issues/491)) ([e556901](https://github.com/k8sgpt-ai/k8sgpt/commit/e556901b9d6205f75c819e1fbde51ba1f018e97d))\n* **deps:** update docker/setup-buildx-action digest to 6a58db7 ([#489](https://github.com/k8sgpt-ai/k8sgpt/issues/489)) ([a23276d](https://github.com/k8sgpt-ai/k8sgpt/commit/a23276d3ff740abc6d3b36a4c793d90387ecee08))\n* **deps:** update goreleaser/goreleaser-action digest to 336e299 ([#495](https://github.com/k8sgpt-ai/k8sgpt/issues/495)) ([ad2a5fd](https://github.com/k8sgpt-ai/k8sgpt/commit/ad2a5fd5fce78bf1bda4a48ad4a21598abadcdf4))\n\n## [0.3.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.5...v0.3.6) (2023-05-31)\n\n\n### Features\n\n* get official field doc ([#457](https://github.com/k8sgpt-ai/k8sgpt/issues/457)) ([f9621af](https://github.com/k8sgpt-ai/k8sgpt/commit/f9621af7e480f490710020b931cbb08fb9824740))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.270 ([#465](https://github.com/k8sgpt-ai/k8sgpt/issues/465)) ([5cf4fc5](https://github.com/k8sgpt-ai/k8sgpt/commit/5cf4fc52da4542a8bae98764d2fa7e337d95e5bd))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.271 ([#469](https://github.com/k8sgpt-ai/k8sgpt/issues/469)) ([1459dd4](https://github.com/k8sgpt-ai/k8sgpt/commit/1459dd4b8eca937e95ebe9b727311dc8b023e304))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.272 ([#473](https://github.com/k8sgpt-ai/k8sgpt/issues/473)) ([5233627](https://github.com/k8sgpt-ai/k8sgpt/commit/523362765f4c064c02798bb9e6f31e2bcc856e5f))\n* **deps:** update module github.com/spf13/viper to v1.16.0 ([#472](https://github.com/k8sgpt-ai/k8sgpt/issues/472)) ([6052a5b](https://github.com/k8sgpt-ai/k8sgpt/commit/6052a5b4d77902e1882e3121b678671c89b57af8))\n* **deps:** update module github.com/stretchr/testify to v1.8.4 ([#471](https://github.com/k8sgpt-ai/k8sgpt/issues/471)) ([42437f7](https://github.com/k8sgpt-ai/k8sgpt/commit/42437f77d1e0735a8f38a62ddbefb4d1f4e61c0e))\n* name of sa reference in deployment ([#468](https://github.com/k8sgpt-ai/k8sgpt/issues/468)) ([cd049c9](https://github.com/k8sgpt-ai/k8sgpt/commit/cd049c9b4b188f702608d989fb32ae62f333dac5))\n* typo ([#463](https://github.com/k8sgpt-ai/k8sgpt/issues/463)) ([1b86a6f](https://github.com/k8sgpt-ai/k8sgpt/commit/1b86a6fc89f90d29fdf2fab87a517f0da225ec96))\n\n\n### Other\n\n* **deps:** update google-github-actions/release-please-action digest to 51ee8ae ([#464](https://github.com/k8sgpt-ai/k8sgpt/issues/464)) ([86ebc23](https://github.com/k8sgpt-ai/k8sgpt/commit/86ebc23de762583b5904605f5651bbc83760aa95))\n\n## [0.3.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.4...v0.3.5) (2023-05-25)\n\n\n### Features\n\n* add configuration api route ([#459](https://github.com/k8sgpt-ai/k8sgpt/issues/459)) ([fa4a075](https://github.com/k8sgpt-ai/k8sgpt/commit/fa4a0757b83f8ec00df951d49316f10961daa0e0))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.267 ([#451](https://github.com/k8sgpt-ai/k8sgpt/issues/451)) ([49e120c](https://github.com/k8sgpt-ai/k8sgpt/commit/49e120c28e8b5ce5a8f7255ebc0f1b1b5c423f95))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.269 ([#458](https://github.com/k8sgpt-ai/k8sgpt/issues/458)) ([2994c1c](https://github.com/k8sgpt-ai/k8sgpt/commit/2994c1c5a77ce6ebe6e59d6edc9647c02f06f261))\n* updated list.go to handle k8sgpt cache list crashing issue ([#455](https://github.com/k8sgpt-ai/k8sgpt/issues/455)) ([6eac58d](https://github.com/k8sgpt-ai/k8sgpt/commit/6eac58d4b03169356d3f06674ef206472e149fde))\n\n## [0.3.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.3...v0.3.4) (2023-05-22)\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.266 ([#446](https://github.com/k8sgpt-ai/k8sgpt/issues/446)) ([edda743](https://github.com/k8sgpt-ai/k8sgpt/commit/edda743fa2bf4b5ae2551c981447a5912a459bb4))\n* **deps:** update module github.com/stretchr/testify to v1.8.3 ([#442](https://github.com/k8sgpt-ai/k8sgpt/issues/442)) ([fe450eb](https://github.com/k8sgpt-ai/k8sgpt/commit/fe450eb69da0645328e60e2d7b0852ffdb996dee))\n\n\n### Other\n\n* add more filter releavent UT in analysis_test.go ([#435](https://github.com/k8sgpt-ai/k8sgpt/issues/435)) ([36995fd](https://github.com/k8sgpt-ai/k8sgpt/commit/36995fd4ed41c8c83ebc308f8ec2a4bfe530f7dc))\n\n## [0.3.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.2...v0.3.3) (2023-05-20)\n\n\n### Features\n\n* caching ([#439](https://github.com/k8sgpt-ai/k8sgpt/issues/439)) ([948dae5](https://github.com/k8sgpt-ai/k8sgpt/commit/948dae5e288ec3bb0165eb3ce32171b12003f9c7))\n* rework auth commands ([#438](https://github.com/k8sgpt-ai/k8sgpt/issues/438)) ([c659a87](https://github.com/k8sgpt-ai/k8sgpt/commit/c659a875fc296849a3703bfd7f0c4796f44bdf5a))\n\n\n### Bug Fixes\n\n* append coreAnalyzer if active_filter is empty and integration is added ([#441](https://github.com/k8sgpt-ai/k8sgpt/issues/441)) ([b7dc384](https://github.com/k8sgpt-ai/k8sgpt/commit/b7dc3845476759bedb3a55f77c8779e4a9f460dd))\n* **deps:** update kubernetes packages to v0.27.2 ([#436](https://github.com/k8sgpt-ai/k8sgpt/issues/436)) ([d13b913](https://github.com/k8sgpt-ai/k8sgpt/commit/d13b91301cab5e05349b68716cd506fa1705f36f))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.44.265 ([#445](https://github.com/k8sgpt-ai/k8sgpt/issues/445)) ([c588e96](https://github.com/k8sgpt-ai/k8sgpt/commit/c588e963de3f238f07200d6cf09fe6f9781484f5))\n* docker version ([#444](https://github.com/k8sgpt-ai/k8sgpt/issues/444)) ([1f767eb](https://github.com/k8sgpt-ai/k8sgpt/commit/1f767ebd2e31e61decab36218b1b85f2b3b6207d))\n* use coreAnalyzer if there are no filters selected and no active_filters ([#432](https://github.com/k8sgpt-ai/k8sgpt/issues/432)) ([f0d3f36](https://github.com/k8sgpt-ai/k8sgpt/commit/f0d3f36f6d56bd76248590c0b841dffb7769a2ee))\n\n## [0.3.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.1...v0.3.2) (2023-05-16)\n\n\n### Features\n\n* added the ability to set a user default AI provider ([#427](https://github.com/k8sgpt-ai/k8sgpt/issues/427)) ([cbe2fb4](https://github.com/k8sgpt-ai/k8sgpt/commit/cbe2fb4a4c160a0a24b3fb4602cae8e5eebd6aa0))\n\n\n### Bug Fixes\n\n* improve default_prompt ([#406](https://github.com/k8sgpt-ai/k8sgpt/issues/406)) ([06542b4](https://github.com/k8sgpt-ai/k8sgpt/commit/06542b4bf1aec193f11a40526a1b60be01972e66))\n* missing validation for backend option in remove command ([#429](https://github.com/k8sgpt-ai/k8sgpt/issues/429)) ([af826d5](https://github.com/k8sgpt-ai/k8sgpt/commit/af826d500fef0469b958250161b0827aa4ab2933))\n\n\n### Other\n\n* **deps:** bump github.com/docker/distribution ([#428](https://github.com/k8sgpt-ai/k8sgpt/issues/428)) ([3099909](https://github.com/k8sgpt-ai/k8sgpt/commit/30999091136c64173e5c15b789036c85f8b855f3))\n* **deps:** update actions/setup-go digest to fac708d ([#422](https://github.com/k8sgpt-ai/k8sgpt/issues/422)) ([097c791](https://github.com/k8sgpt-ai/k8sgpt/commit/097c7912b0572d0461f08af12e9f21c6618c13e4))\n* **deps:** update reviewdog/action-golangci-lint digest to 79d32f1 ([#425](https://github.com/k8sgpt-ai/k8sgpt/issues/425)) ([032576c](https://github.com/k8sgpt-ai/k8sgpt/commit/032576c728751522fe6cd8f255366cc3d5c0f23c))\n\n## [0.3.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.0...v0.3.1) (2023-05-15)\n\n\n### Features\n\n* add error message if analyze request fail ([#393](https://github.com/k8sgpt-ai/k8sgpt/issues/393)) ([639aa12](https://github.com/k8sgpt-ai/k8sgpt/commit/639aa12931b3fc9f99a4b34f33f583b9f427f40c))\n* add remove command to remove a backend AI provider ([#395](https://github.com/k8sgpt-ai/k8sgpt/issues/395)) ([c5b72ee](https://github.com/k8sgpt-ai/k8sgpt/commit/c5b72eee165a29d4ed59b0ec2f19e9f58267840d))\n* filters api ([#407](https://github.com/k8sgpt-ai/k8sgpt/issues/407)) ([e5e613a](https://github.com/k8sgpt-ai/k8sgpt/commit/e5e613acee0a99f108dd90affc55a18dee42ad9c))\n* use correct port to metrics ([#390](https://github.com/k8sgpt-ai/k8sgpt/issues/390)) ([5d4e591](https://github.com/k8sgpt-ai/k8sgpt/commit/5d4e591f11e555cac851205fff158ef22f702c33))\n\n\n### Bug Fixes\n\n* clusterole name ([#392](https://github.com/k8sgpt-ai/k8sgpt/issues/392)) ([123b8a6](https://github.com/k8sgpt-ai/k8sgpt/commit/123b8a66eed1af41b7bd4e558ba4f0f8ef947e98))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20230515081240-6b5b845c638e.1 ([#397](https://github.com/k8sgpt-ai/k8sgpt/issues/397)) ([a1f98ad](https://github.com/k8sgpt-ai/k8sgpt/commit/a1f98ad78ecd9a84d26e7a59c340e5410524e561))\n* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go to v1.28.1-20230510140658-54288a50e81c.4 ([#398](https://github.com/k8sgpt-ai/k8sgpt/issues/398)) ([50916f2](https://github.com/k8sgpt-ai/k8sgpt/commit/50916f2c93fb19b935f835eda1ba78b7c0465fe6))\n* **deps:** update module google.golang.org/grpc to v1.55.0 ([#389](https://github.com/k8sgpt-ai/k8sgpt/issues/389)) ([8cfb717](https://github.com/k8sgpt-ai/k8sgpt/commit/8cfb717dc15a6af489a16282d38d3495518bc919))\n* **deps:** update module helm.sh/helm/v3 to v3.12.0 ([#396](https://github.com/k8sgpt-ai/k8sgpt/issues/396)) ([c1410d1](https://github.com/k8sgpt-ai/k8sgpt/commit/c1410d169945341e9a635e12c2adcc87a08f8a09))\n* update engine's cmd flag to match the new cli layout ([#400](https://github.com/k8sgpt-ai/k8sgpt/issues/400)) ([aafe669](https://github.com/k8sgpt-ai/k8sgpt/commit/aafe669739aa8c38611d13deb08706096c7893e0))\n\n\n### Other\n\n* gofmt fix and enable in CI ([#414](https://github.com/k8sgpt-ai/k8sgpt/issues/414)) ([e66de8c](https://github.com/k8sgpt-ai/k8sgpt/commit/e66de8c4cea1213cda1db609f07cbb0c8f6591c3))\n* make go-lint happy ([#405](https://github.com/k8sgpt-ai/k8sgpt/issues/405)) ([ed73485](https://github.com/k8sgpt-ai/k8sgpt/commit/ed73485d92af0329915633d51c7eccdbce9b37cc))\n\n## [0.3.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.9...v0.3.0) (2023-05-09)\n\n\n### ⚠ BREAKING CHANGES\n\n* migrate api to grpc ([#386](https://github.com/k8sgpt-ai/k8sgpt/issues/386))\n\n### Features\n\n* add auth commands ([#369](https://github.com/k8sgpt-ai/k8sgpt/issues/369)) ([00aaae8](https://github.com/k8sgpt-ai/k8sgpt/commit/00aaae86d88812bd6b6be290ba440ea583ccc01c))\n* migrate api to grpc ([#386](https://github.com/k8sgpt-ai/k8sgpt/issues/386)) ([9998e76](https://github.com/k8sgpt-ai/k8sgpt/commit/9998e7620d2803b82b241482649449507040add3))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/sashabaranov/go-openai to v1.9.3 ([#378](https://github.com/k8sgpt-ai/k8sgpt/issues/378)) ([045a063](https://github.com/k8sgpt-ai/k8sgpt/commit/045a06350bf41d4177e67316978af8fcf02ff19a))\n* **deps:** update module golang.org/x/term to v0.8.0 ([#382](https://github.com/k8sgpt-ai/k8sgpt/issues/382)) ([65fff11](https://github.com/k8sgpt-ai/k8sgpt/commit/65fff11e585f8074fb77124b25339a09da313970))\n\n\n### Docs\n\n* update README ([#383](https://github.com/k8sgpt-ai/k8sgpt/issues/383)) ([d6bcb96](https://github.com/k8sgpt-ai/k8sgpt/commit/d6bcb96105a549eb772b790704c7ec27e374eafd))\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.14.2 ([#387](https://github.com/k8sgpt-ai/k8sgpt/issues/387)) ([9192b26](https://github.com/k8sgpt-ai/k8sgpt/commit/9192b26fab2ce09c8a480256a15723ae788612c3))\n* fix the logo URL ([#384](https://github.com/k8sgpt-ai/k8sgpt/issues/384)) ([b6b0612](https://github.com/k8sgpt-ai/k8sgpt/commit/b6b06123db8914cae09dfa96edd92aff83823bdf))\n\n## [0.2.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.8...v0.2.9) (2023-05-03)\n\n\n### Features\n\n* add additionalLabels to Service Monitor ([#366](https://github.com/k8sgpt-ai/k8sgpt/issues/366)) ([a89a5cf](https://github.com/k8sgpt-ai/k8sgpt/commit/a89a5cfa2efd365cfc1c501c7055a315949c5f97))\n* add azure openai provider ([#309](https://github.com/k8sgpt-ai/k8sgpt/issues/309)) ([d8357ce](https://github.com/k8sgpt-ai/k8sgpt/commit/d8357ceb949e04d9dd21276a1d1dfcb60010c37a))\n* add helm chart ([#318](https://github.com/k8sgpt-ai/k8sgpt/issues/318)) ([5af8178](https://github.com/k8sgpt-ai/k8sgpt/commit/5af817880278771cf0b25d0936b90a45c089218c))\n* improving readme ([7471b76](https://github.com/k8sgpt-ai/k8sgpt/commit/7471b7679469bc7416ee35061a13e8442bfc532c))\n* rework output format ([#368](https://github.com/k8sgpt-ai/k8sgpt/issues/368)) ([8b49f70](https://github.com/k8sgpt-ai/k8sgpt/commit/8b49f708f364569994801757cf72982291c0de82))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.13.2 ([#353](https://github.com/k8sgpt-ai/k8sgpt/issues/353)) ([363294c](https://github.com/k8sgpt-ai/k8sgpt/commit/363294c310ca1a6666d4d2dcba7e232387f0d41f))\n* **deps:** update module github.com/prometheus/client_golang to v1.15.1 ([#374](https://github.com/k8sgpt-ai/k8sgpt/issues/374)) ([799869b](https://github.com/k8sgpt-ai/k8sgpt/commit/799869bcef76be0d5b2169646de106bd3b441c50))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.9.1 ([#363](https://github.com/k8sgpt-ai/k8sgpt/issues/363)) ([766a15b](https://github.com/k8sgpt-ai/k8sgpt/commit/766a15b25a2669b101c717013dae0ea4f5d0daa3))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.9.2 ([#375](https://github.com/k8sgpt-ai/k8sgpt/issues/375)) ([8b82d59](https://github.com/k8sgpt-ai/k8sgpt/commit/8b82d59bd7a6837a543467f9d76c66e70f267b85))\n* update CONTRIBUTING.md Slack URL ([#373](https://github.com/k8sgpt-ai/k8sgpt/issues/373)) ([e0200e7](https://github.com/k8sgpt-ai/k8sgpt/commit/e0200e7fa05597b52c9f25fc2e9b744aea764b9a))\n\n\n### Docs\n\n* remove issue templates to use org wide ones ([#352](https://github.com/k8sgpt-ai/k8sgpt/issues/352)) ([3051b1c](https://github.com/k8sgpt-ai/k8sgpt/commit/3051b1ca343d739e48ff05c96468040adefc929a))\n* update README.md ([#356](https://github.com/k8sgpt-ai/k8sgpt/issues/356)) ([4e146fb](https://github.com/k8sgpt-ai/k8sgpt/commit/4e146fb152b79a43f3163a94389022b4325126d4))\n\n\n### Other\n\n* added changing banners ([#367](https://github.com/k8sgpt-ai/k8sgpt/issues/367)) ([4f6e833](https://github.com/k8sgpt-ai/k8sgpt/commit/4f6e833d3427adf82438186f52eee40293c50cd0))\n* **deps:** update golang docker tag to v1.20.4 ([#370](https://github.com/k8sgpt-ai/k8sgpt/issues/370)) ([9faa694](https://github.com/k8sgpt-ai/k8sgpt/commit/9faa69422dab812ec062c10a8fffb73160a3cd21))\n* **deps:** update reviewdog/action-golangci-lint digest to f5d8591 ([#362](https://github.com/k8sgpt-ai/k8sgpt/issues/362)) ([b8a5f3b](https://github.com/k8sgpt-ai/k8sgpt/commit/b8a5f3bab85e28f83259739958fe22ca0ada211a))\n* updated logo ([#365](https://github.com/k8sgpt-ai/k8sgpt/issues/365)) ([6431be7](https://github.com/k8sgpt-ai/k8sgpt/commit/6431be7771bd7c34d6beac14470bc6918b1793c4))\n\n## [0.2.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.7...v0.2.8) (2023-04-27)\n\n\n### Features\n\n* don't ask for password if backend is localai ([74d9a75](https://github.com/k8sgpt-ai/k8sgpt/commit/74d9a750ca01361eb81fdcc91eb5886ecff1d17c))\n* introduce linter to run on PR builds ([#333](https://github.com/k8sgpt-ai/k8sgpt/issues/333)) ([252c734](https://github.com/k8sgpt-ai/k8sgpt/commit/252c7343106bf64c86861a9452e8618efc72881c)), closes [#330](https://github.com/k8sgpt-ai/k8sgpt/issues/330)\n\n\n### Bug Fixes\n\n* remove dead code ([c29860d](https://github.com/k8sgpt-ai/k8sgpt/commit/c29860d418faa316bc167721e443f7b64eafd970))\n* report failure if network policy doesn't match any pods ([8adde6b](https://github.com/k8sgpt-ai/k8sgpt/commit/8adde6bf873b46f365146bc14fc4c8f46d82f8dc))\n* take `KUBECONFIG` env variable into consideration ([#340](https://github.com/k8sgpt-ai/k8sgpt/issues/340)) ([ee85d13](https://github.com/k8sgpt-ai/k8sgpt/commit/ee85d13d59e045519b087adaf55520acc2c205db)), closes [#331](https://github.com/k8sgpt-ai/k8sgpt/issues/331)\n* use a cache file name with a fixed size. ([#350](https://github.com/k8sgpt-ai/k8sgpt/issues/350)) ([dee4235](https://github.com/k8sgpt-ai/k8sgpt/commit/dee423519eb35f11c3e3a6dd64981e781899fe22))\n* use correct result slice for cronjob analyzer ([947e94f](https://github.com/k8sgpt-ai/k8sgpt/commit/947e94f35379712a2fb1e2a2c90636606e0e44b6))\n\n\n### Docs\n\n* fix README ([#345](https://github.com/k8sgpt-ai/k8sgpt/issues/345)) ([f8fa35c](https://github.com/k8sgpt-ai/k8sgpt/commit/f8fa35cf9d591691679d6881fcc203e3411d99aa))\n\n\n### Other\n\n* add settings ([#351](https://github.com/k8sgpt-ai/k8sgpt/issues/351)) ([3af3667](https://github.com/k8sgpt-ai/k8sgpt/commit/3af366788fb47ff87be0142446c027f5a90491e7))\n* **deps:** pin dependencies ([#336](https://github.com/k8sgpt-ai/k8sgpt/issues/336)) ([125341b](https://github.com/k8sgpt-ai/k8sgpt/commit/125341bdaacbc8bedbb333e498dabfb5c72a24c0))\n* logo update ([#339](https://github.com/k8sgpt-ai/k8sgpt/issues/339)) ([d4dcc7a](https://github.com/k8sgpt-ai/k8sgpt/commit/d4dcc7a3991a861923c8115c0c82759b9e83bcfa))\n* update Apache2 license ([#342](https://github.com/k8sgpt-ai/k8sgpt/issues/342)) ([aca5806](https://github.com/k8sgpt-ai/k8sgpt/commit/aca58064c36b3bc13699e055a7cca8a493320078))\n* update README.md ([#346](https://github.com/k8sgpt-ai/k8sgpt/issues/346)) ([14a3537](https://github.com/k8sgpt-ai/k8sgpt/commit/14a3537ce9bc9d581b78329be899a66bc14db648))\n* updated banner ([#343](https://github.com/k8sgpt-ai/k8sgpt/issues/343)) ([0995e00](https://github.com/k8sgpt-ai/k8sgpt/commit/0995e008fe64f5978c3a0cc9fb4c525470f00dfa))\n\n## [0.2.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.6...v0.2.7) (2023-04-25)\n\n\n### Bug Fixes\n\n* remove pointer to loop variable when searching the latest event to analyze ([#328](https://github.com/k8sgpt-ai/k8sgpt/issues/328)) ([2616220](https://github.com/k8sgpt-ai/k8sgpt/commit/2616220935d450030c8a9f2f2741c3607aa4b663))\n\n## [0.2.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.5...v0.2.6) (2023-04-25)\n\n\n### Bug Fixes\n\n* explicitly pass in filter to async analysis go routine ([#326](https://github.com/k8sgpt-ai/k8sgpt/issues/326)) ([692cd06](https://github.com/k8sgpt-ai/k8sgpt/commit/692cd06c385c1c6f458994f6e975a9fce2bc1c57))\n\n## [0.2.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.4...v0.2.5) (2023-04-25)\n\n\n### Features\n\n* add configuration interface to support customer providers ([84a3cc0](https://github.com/k8sgpt-ai/k8sgpt/commit/84a3cc05fb6e21b732ef777351b42db8045e1093))\n* add k8sgpt grafana dashboard ([#316](https://github.com/k8sgpt-ai/k8sgpt/issues/316)) ([ff79982](https://github.com/k8sgpt-ai/k8sgpt/commit/ff799825cfe5856bb97c8f38d939ec36b19fa30a))\n* add serve & integration to README ([a65ee7f](https://github.com/k8sgpt-ai/k8sgpt/commit/a65ee7fc0957c7ba9369bdbe12e648818ca3f841))\n* add subproject group to CODEOWNERS ([#322](https://github.com/k8sgpt-ai/k8sgpt/issues/322)) ([2391603](https://github.com/k8sgpt-ai/k8sgpt/commit/2391603075e73b91d9988d40eecddfc3e0593405))\n* allow to set a baseurl ([#310](https://github.com/k8sgpt-ai/k8sgpt/issues/310)) ([cf797a6](https://github.com/k8sgpt-ai/k8sgpt/commit/cf797a6eb67efba957704077b4b04ed3ee166c24))\n* async calls ([#311](https://github.com/k8sgpt-ai/k8sgpt/issues/311)) ([c3cc413](https://github.com/k8sgpt-ai/k8sgpt/commit/c3cc413e7fc3b06b310779dfa3cb4863ea9f3ed2))\n* modify error handling to return a list of errors to display to the user at the end of analysis without blocking it if an error is detected (e.g., version of an object is not available on user's cluster) ([fa087ff](https://github.com/k8sgpt-ai/k8sgpt/commit/fa087ff5593871d2a07d68f203dd91e66c57e40b))\n* the overall optimization and architecture design of the makefile are made ([#317](https://github.com/k8sgpt-ai/k8sgpt/issues/317)) ([754bf91](https://github.com/k8sgpt-ai/k8sgpt/commit/754bf917e1ac524699d38fb2dc59bc5d858f6d80))\n* update readme ([#314](https://github.com/k8sgpt-ai/k8sgpt/issues/314)) ([ddd830c](https://github.com/k8sgpt-ai/k8sgpt/commit/ddd830cc569278c157480c44a671c9be20c95b24))\n* use OS conform path for storing cached results ([7eddb8f](https://github.com/k8sgpt-ai/k8sgpt/commit/7eddb8f4a6dc61d5f66fc1bf56c0e8cbf9370229)), closes [#323](https://github.com/k8sgpt-ai/k8sgpt/issues/323)\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/aquasecurity/trivy-operator to v0.13.1 ([#321](https://github.com/k8sgpt-ai/k8sgpt/issues/321)) ([e7f74db](https://github.com/k8sgpt-ai/k8sgpt/commit/e7f74db6e556146b898437bb777c2b803d1bec4f))\n* **deps:** update module github.com/prometheus/client_golang to v1.15.0 ([#303](https://github.com/k8sgpt-ai/k8sgpt/issues/303)) ([df2ed41](https://github.com/k8sgpt-ai/k8sgpt/commit/df2ed4185b5a33a18e6b144c85bec3902c14d209))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.9.0 ([#298](https://github.com/k8sgpt-ai/k8sgpt/issues/298)) ([0472c36](https://github.com/k8sgpt-ai/k8sgpt/commit/0472c363a4d8a90556bc744fbf513ad63281e38b))\n\n\n### Other\n\n* add serviceMonitor in sample yaml ([#304](https://github.com/k8sgpt-ai/k8sgpt/issues/304)) ([0a4ed0d](https://github.com/k8sgpt-ai/k8sgpt/commit/0a4ed0d907c22a924dd79e8945eb9d6d10cd9ce7))\n* analyze Pod ReadinessProbe faliure ([3c7e0bb](https://github.com/k8sgpt-ai/k8sgpt/commit/3c7e0bba1d4cc8247d248756dcfef884bc406992))\n* change license to Apache-2 ([#313](https://github.com/k8sgpt-ai/k8sgpt/issues/313)) ([d0f7a11](https://github.com/k8sgpt-ai/k8sgpt/commit/d0f7a1105fe7ed317785782d3af45c83766b7d80))\n\n## [0.2.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.3...v0.2.4) (2023-04-18)\n\n\n### Features\n\n* improve HPA analyzer to check ScaleTargetRef resources  ([#283](https://github.com/k8sgpt-ai/k8sgpt/issues/283)) ([7173203](https://github.com/k8sgpt-ai/k8sgpt/commit/71732037fa40071cef0c2bc143736019d75eac86))\n* init logging middleware on server mode ([6742410](https://github.com/k8sgpt-ai/k8sgpt/commit/6742410025d5e99c60045bb314730799f0e1e5ce))\n\n\n### Bug Fixes\n\n* deployment/cronjob namespace filtering ([#290](https://github.com/k8sgpt-ai/k8sgpt/issues/290)) ([3d684a2](https://github.com/k8sgpt-ai/k8sgpt/commit/3d684a2af7a9e1821bdb8b1bd6e85867b800d3ee))\n* ensure parent directories are created in EnsureDirExists function ([#293](https://github.com/k8sgpt-ai/k8sgpt/issues/293)) ([af8b350](https://github.com/k8sgpt-ai/k8sgpt/commit/af8b350520d1a187a199482dd338db0086118db8))\n* resolve language toggle bug (issue [#294](https://github.com/k8sgpt-ai/k8sgpt/issues/294)) ([0313627](https://github.com/k8sgpt-ai/k8sgpt/commit/03136278486ba12e3352580b317b9e63fa3a80f0))\n\n## [0.2.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.2...v0.2.3) (2023-04-16)\n\n\n### Features\n\n* add node analyzer ([#272](https://github.com/k8sgpt-ai/k8sgpt/issues/272)) ([6247a1c](https://github.com/k8sgpt-ai/k8sgpt/commit/6247a1c0f3c2ead6a59661afed06973c29e57eca))\n* add output query param on serve mode & refactor output logic ([9642202](https://github.com/k8sgpt-ai/k8sgpt/commit/9642202ed1b09c06a687651b7818c2a4df8a0c06))\n* add server metrics ([#273](https://github.com/k8sgpt-ai/k8sgpt/issues/273)) ([a3becc9](https://github.com/k8sgpt-ai/k8sgpt/commit/a3becc9906515d0567808fee9a4e322451d6dc3f))\n* envs to initialise server ([0071e25](https://github.com/k8sgpt-ai/k8sgpt/commit/0071e25992fc86c3882c2066873a2b04b43fe476))\n* rename server/main.go to server/server.go ([9121a98](https://github.com/k8sgpt-ai/k8sgpt/commit/9121a983e52fa15c07bcc3bb361df97b8085c24c))\n* running in cluster ([842f08c](https://github.com/k8sgpt-ai/k8sgpt/commit/842f08c655fde66b6b628192490e50be2ac3dcef))\n* running in cluster ([3988eb2](https://github.com/k8sgpt-ai/k8sgpt/commit/3988eb2fd0a7d29ffa7b7bbc59960ca91e50466e))\n* switch config file to XDG conform location ([dee4355](https://github.com/k8sgpt-ai/k8sgpt/commit/dee435514d7f717e4eb63b15a9d9fdb0722330ac))\n* wip blocked until we have envs ([fe2c08c](https://github.com/k8sgpt-ai/k8sgpt/commit/fe2c08cf72a6ca271d1b431be66653f1396f304d))\n\n\n### Bug Fixes\n\n* add new line after version cmd output ([92e7b3d](https://github.com/k8sgpt-ai/k8sgpt/commit/92e7b3d3fb00c33ac48230caac34f45729e2f6b2))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.8.0 ([#277](https://github.com/k8sgpt-ai/k8sgpt/issues/277)) ([51b1b35](https://github.com/k8sgpt-ai/k8sgpt/commit/51b1b352acd24ebdc4cf9d9121f25c90e8f76ba7))\n* resolve issue with duplicated integration filters. ([960ba56](https://github.com/k8sgpt-ai/k8sgpt/commit/960ba568d0dcc2ace722dc5c9b7c846366a98070))\n* use the aiProvider object when launching the server instead of the deprecated configuration keys ([e7076ed](https://github.com/k8sgpt-ai/k8sgpt/commit/e7076ed6093aa9609d8c884b7a03e295057aaa8e))\n\n\n### Other\n\n* updated ([f0a0c9a](https://github.com/k8sgpt-ai/k8sgpt/commit/f0a0c9aebf627d65b0192ba3d0786cefd81e1fef))\n\n## [0.2.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.1...v0.2.2) (2023-04-14)\n\n\n### Features\n\n* add simple health endpoint ([26c0cb2](https://github.com/k8sgpt-ai/k8sgpt/commit/26c0cb2eed75695220007e6d6f7b492c2641a149))\n* anoymization based on pr feedback ([19e1b94](https://github.com/k8sgpt-ai/k8sgpt/commit/19e1b94e7c9ce4092f1dabd659023a193b2c4a92))\n* anoymization based on pr feedback ([fe52951](https://github.com/k8sgpt-ai/k8sgpt/commit/fe529510b68ac5fbd39c147c7719abe2e7d20894))\n* check for auth only in case of --explain ([57790e5](https://github.com/k8sgpt-ai/k8sgpt/commit/57790e5bc7037f57a4f73248fe05cac192511470))\n* first version of serve ([b2e8add](https://github.com/k8sgpt-ai/k8sgpt/commit/b2e8adda333fbd508f0f01f2afcabc57bf9948c2))\n* unified cmd and api ([9157d4d](https://github.com/k8sgpt-ai/k8sgpt/commit/9157d4dd1312bf75b336beb0e097422b303d22f1))\n* updated api ([adae2ef](https://github.com/k8sgpt-ai/k8sgpt/commit/adae2ef71d81431711c552159362336e496b21ee))\n\n\n### Bug Fixes\n\n* bool conversion ([336ec2a](https://github.com/k8sgpt-ai/k8sgpt/commit/336ec2a42693d0df325b95cbebd9545b19e27725))\n* **deps:** update module helm.sh/helm/v3 to v3.11.3 ([4dd91ed](https://github.com/k8sgpt-ai/k8sgpt/commit/4dd91ed8263292476054bc70d3d6a3149f88f1b3))\n* naming ([159b385](https://github.com/k8sgpt-ai/k8sgpt/commit/159b3851ec54e93a447b0f13aa4ceb7b8b8f62db))\n* start message ([6b63027](https://github.com/k8sgpt-ai/k8sgpt/commit/6b630275eb64b799c50e3074cb22a3b41bb893de))\n\n\n### Docs\n\n* fix Slack link ([1dccaea](https://github.com/k8sgpt-ai/k8sgpt/commit/1dccaea3f4f96b2da52999eed5031f02a89c0b6e))\n\n\n### Other\n\n* added oidc ([bffad41](https://github.com/k8sgpt-ai/k8sgpt/commit/bffad41134d231b16f136a619174ff3bee61765a))\n* additional analyzers ([23071fd](https://github.com/k8sgpt-ai/k8sgpt/commit/23071fd2e6b421f0f5fcd6e7e4985c6900e5405c))\n* **deps:** bump github.com/docker/docker ([#268](https://github.com/k8sgpt-ai/k8sgpt/issues/268)) ([7d1e2ac](https://github.com/k8sgpt-ai/k8sgpt/commit/7d1e2acaf3eaf00929ff43b9373df6a4be100795))\n* **deps:** update actions/checkout digest to 83b7061 ([cbe6f27](https://github.com/k8sgpt-ai/k8sgpt/commit/cbe6f27c05e82f55f41b648b01972ba2c43f1534))\n* **deps:** update actions/checkout digest to 8e5e7e5 ([#266](https://github.com/k8sgpt-ai/k8sgpt/issues/266)) ([0af34a1](https://github.com/k8sgpt-ai/k8sgpt/commit/0af34a1a95502dc26d7e08bac896f691e4969090))\n* **deps:** update module oras.land/oras-go to v1.2.3 ([#249](https://github.com/k8sgpt-ai/k8sgpt/issues/249)) ([13c9231](https://github.com/k8sgpt-ai/k8sgpt/commit/13c9231aafef3a259fd678a80063ad2e968d6e95))\n* fixing up tests ([f9b25d9](https://github.com/k8sgpt-ai/k8sgpt/commit/f9b25d9e85a8faaf1aae59d7bedc4c0f3538181e))\n* fixing up tests ([498d454](https://github.com/k8sgpt-ai/k8sgpt/commit/498d454c174c7d39da1ca63b2a201e797d7e5e1c))\n* Merge branch 'main' into feat/additional-analyzers ([4d36248](https://github.com/k8sgpt-ai/k8sgpt/commit/4d3624830ff840f9ccf11d7da20953bdf4c7c7fc))\n* removing field ([ddb51c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ddb51c7af470044a8514ed013b44cc135e4c0f10))\n\n## [0.2.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.0...v0.2.1) (2023-04-12)\n\n\n### Features\n\n* add anonymization example to README ([8a60b57](https://github.com/k8sgpt-ai/k8sgpt/commit/8a60b579409c67f092156ba1adf1be22cce37b8c))\n* add anonymization flag ([d2a84ea](https://github.com/k8sgpt-ai/k8sgpt/commit/d2a84ea2b5c800dd900aac3a48b1914bd9ddb917))\n* add more details on anonymize flag ([b687473](https://github.com/k8sgpt-ai/k8sgpt/commit/b687473e6169406002b0ee8be6ebb9ce43b46495))\n* add storage class names' check. ([c8ba7d6](https://github.com/k8sgpt-ai/k8sgpt/commit/c8ba7d62d2f1d262263d1dff8f980e91cdcd50e8))\n* improve documentation ([6f08654](https://github.com/k8sgpt-ai/k8sgpt/commit/6f0865413fc2854450d217225199cec199972490))\n* improve documentation & update hpa message ([11326c1](https://github.com/k8sgpt-ai/k8sgpt/commit/11326c1c5f307c718e8d1e56099537314ffedadd))\n* improve security of the MaskString function ([08f2a89](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2a89e54a65544322814286977b2c05acce89d))\n* initial impl of integration ([b0e5170](https://github.com/k8sgpt-ai/k8sgpt/commit/b0e517006e65ac2b4e2d4e2696531d4bbf62c34b))\n* initial impl of integration ([61d6e52](https://github.com/k8sgpt-ai/k8sgpt/commit/61d6e524657272cf3a967c724f212677fcfe7d2b))\n* integration ready for first review ([3682f5c](https://github.com/k8sgpt-ai/k8sgpt/commit/3682f5c7ebb9590e92162eed214a8127f71bcd81))\n* introduce StatefulSet analyser. ([c041ce2](https://github.com/k8sgpt-ai/k8sgpt/commit/c041ce2bbb4ecbc6f5637207c9f3071eee022744))\n* refactor integration to use Failure object ([c0afc0f](https://github.com/k8sgpt-ai/k8sgpt/commit/c0afc0f5c91cfa50b1f7af901800ff0a2b492d18))\n* return errors if filter specified by flag does not exist. ([dd5824f](https://github.com/k8sgpt-ai/k8sgpt/commit/dd5824f4365b01e3c501d8b5cda914dff138e03d))\n\n\n### Bug Fixes\n\n* **deps:** update kubernetes packages to v0.27.0 ([7a97034](https://github.com/k8sgpt-ai/k8sgpt/commit/7a97034cf41cb265111c752ee3d54fd90524ef59))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.7.0 ([#227](https://github.com/k8sgpt-ai/k8sgpt/issues/227)) ([5f3a5a5](https://github.com/k8sgpt-ai/k8sgpt/commit/5f3a5a54a02967acce40f8b4e9dd3a154c83f58c))\n* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([fe261b3](https://github.com/k8sgpt-ai/k8sgpt/commit/fe261b375f4d7990906620f53ac26e792a34731b))\n* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([ab55f15](https://github.com/k8sgpt-ai/k8sgpt/commit/ab55f157ef026502d29eadf5ad83e917fe085a6c))\n* improve ReplaceIfMatch regex ([fd936ce](https://github.com/k8sgpt-ai/k8sgpt/commit/fd936ceaf725d1c1ed1f53eaa2204455dcd1e2af))\n* pdb test ([705d2a0](https://github.com/k8sgpt-ai/k8sgpt/commit/705d2a0dcebb63783782e06b6b775393daf1efb7))\n* use hpa namespace instead analyzer namespace ([#230](https://github.com/k8sgpt-ai/k8sgpt/issues/230)) ([a582d44](https://github.com/k8sgpt-ai/k8sgpt/commit/a582d444c5c53f25d7172947c690b35cad2cc176))\n\n\n### Docs\n\n* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([b45ff1a](https://github.com/k8sgpt-ai/k8sgpt/commit/b45ff1aa8ef447df2b74bb8c6225e2f3d7c5bd63))\n* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([ba01bd4](https://github.com/k8sgpt-ai/k8sgpt/commit/ba01bd4b6ecd64fbe249be54f20471afc6339208))\n\n\n### Other\n\n* add fakeai provider ([#218](https://github.com/k8sgpt-ai/k8sgpt/issues/218)) ([e449cb6](https://github.com/k8sgpt-ai/k8sgpt/commit/e449cb60230d440d5b8e00062db63de5d6d413bf))\n* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([db1388f](https://github.com/k8sgpt-ai/k8sgpt/commit/db1388fd20dcf21069adcecd2796f2e1231162c8))\n* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([992b107](https://github.com/k8sgpt-ai/k8sgpt/commit/992b107c2d906663bb22998004a0859bccd45c77))\n* compiling successfully ([80ac51c](https://github.com/k8sgpt-ai/k8sgpt/commit/80ac51c804351226e1764e3e649ac56e22de3749))\n* **deps:** update anchore/sbom-action action to v0.14.1 ([#228](https://github.com/k8sgpt-ai/k8sgpt/issues/228)) ([9423b53](https://github.com/k8sgpt-ai/k8sgpt/commit/9423b53c1dbae3d0762420a0bacbdace9a2c18c9))\n* **deps:** update google-github-actions/release-please-action digest to c078ea3 ([a1d8012](https://github.com/k8sgpt-ai/k8sgpt/commit/a1d8012a5c748aee3f16621d6da9a0f0c8cba293))\n* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([55dda43](https://github.com/k8sgpt-ai/k8sgpt/commit/55dda432ab89c4917bd28fceabcbe5569c0bf530))\n* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([21dc61c](https://github.com/k8sgpt-ai/k8sgpt/commit/21dc61c04f4d772b5147b38a4d28e5dbddf5cdd8))\n* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([428c348](https://github.com/k8sgpt-ai/k8sgpt/commit/428c3485868a7be95ea6776694e30b36badf4b5c))\n* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([3845d47](https://github.com/k8sgpt-ai/k8sgpt/commit/3845d4747f4e0fc823d1bcf631d6ecdd5e4ccd03))\n* Fixing broken tests ([c809af3](https://github.com/k8sgpt-ai/k8sgpt/commit/c809af3f47388599fda3a88a4638feae1dc90492))\n* fixing filters ([258c69a](https://github.com/k8sgpt-ai/k8sgpt/commit/258c69a17c977867dfd0a7ad02727270b7c172e7))\n* fixing filters ([4d20f70](https://github.com/k8sgpt-ai/k8sgpt/commit/4d20f70fb40ff326ceb279f699068ec4956a2f10))\n* merged ([096321b](https://github.com/k8sgpt-ai/k8sgpt/commit/096321b31a6cf0d53b1861a3e4ad1efe84f697cc))\n* updated analysis_test.go ([825e9a4](https://github.com/k8sgpt-ai/k8sgpt/commit/825e9a43bd3ab7aa3ea52f315993cd778ea039e3))\n* updated link output ([1b7f4ce](https://github.com/k8sgpt-ai/k8sgpt/commit/1b7f4ce44a499e5389aec42fdee00bfa81ef0888))\n* updating based on feedback ([5e5d4b6](https://github.com/k8sgpt-ai/k8sgpt/commit/5e5d4b6de160dc7533067e1c0d8403c3faac1a9f))\n* weird new line after filter removed ([fabe01a](https://github.com/k8sgpt-ai/k8sgpt/commit/fabe01aa019f1db45ed2ff780f0d6d63297b230b))\n\n## [0.2.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.8...v0.2.0) (2023-04-05)\n\n\n### ⚠ BREAKING CHANGES\n\n* The format of the configuration file has changed. Users must update their configuration files to use the new format.\n\n### Features\n\n* add support for new configuration format ([9b243cd](https://github.com/k8sgpt-ai/k8sgpt/commit/9b243cdcaab1742fca2516bc1ae5710505e0eb65))\n* add tests for hpa analyzer ([5a59abb](https://github.com/k8sgpt-ai/k8sgpt/commit/5a59abb55d9e76f3095b903ea973138b1afdccf2))\n* add tests for ingress analyzer && Use t.Fatalf to report a fatal error if RunAnalysis returns an unexpected error ([e27e940](https://github.com/k8sgpt-ai/k8sgpt/commit/e27e9409dcaad78eefd79659e91364617407ae59))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/sashabaranov/go-openai to v1.6.1 ([#207](https://github.com/k8sgpt-ai/k8sgpt/issues/207)) ([eeac731](https://github.com/k8sgpt-ai/k8sgpt/commit/eeac731858999f6f462a7b6ccf210af603674b30))\n* **deps:** update module github.com/spf13/cobra to v1.7.0 ([5d5e082](https://github.com/k8sgpt-ai/k8sgpt/commit/5d5e082f417905954be33b3a620efef674f2588d))\n* **deps:** update module github.com/stretchr/testify to v1.8.2 ([f5e3ca0](https://github.com/k8sgpt-ai/k8sgpt/commit/f5e3ca0bcab9325145a2e1d8624f585ffee8e29f))\n* **deps:** update module golang.org/x/term to v0.7.0 ([8ab3573](https://github.com/k8sgpt-ai/k8sgpt/commit/8ab3573e13a3e3ab98e6f0aa76e429117c888f7f))\n* details in json output ([2f21002](https://github.com/k8sgpt-ai/k8sgpt/commit/2f2100289953af7820bbb01f2c980cf5492de079))\n* fixed hpa tests after rebase ([a24d1f1](https://github.com/k8sgpt-ai/k8sgpt/commit/a24d1f1b304e9448e63c1b7fc283b4cc8bc639aa))\n* regression on dynamic filters ([93bcc62](https://github.com/k8sgpt-ai/k8sgpt/commit/93bcc627ba64a9139e65290a8512e0a9b4bf1a69))\n* Spelling ([ba4d701](https://github.com/k8sgpt-ai/k8sgpt/commit/ba4d7016814ce97353e98658d5bbcd692007e4a9))\n\n\n### Docs\n\n* add curl command and release-please annoations ([1849209](https://github.com/k8sgpt-ai/k8sgpt/commit/184920988f7da928cca7fae4a676e4ee5f13cad1))\n* add guide to details block ([ddc120e](https://github.com/k8sgpt-ai/k8sgpt/commit/ddc120e7c2657385737d3490def28dbabdd2242d))\n* add installation guide via packages ([8e4ce6a](https://github.com/k8sgpt-ai/k8sgpt/commit/8e4ce6a974813258fb9cbeabbcaa3b8f6966a748))\n* minor change ([53c1330](https://github.com/k8sgpt-ai/k8sgpt/commit/53c13305383eb454fe45fefa3483cef4821d5d34))\n* modify README ([fc47c58](https://github.com/k8sgpt-ai/k8sgpt/commit/fc47c58ae1c2b5511ebbe0ed35714e4ecbb4bb7a))\n* modify README ([0f46ceb](https://github.com/k8sgpt-ai/k8sgpt/commit/0f46ceb4456a90e7e05aeff23d25d5775bbf9c2b))\n\n\n### Other\n\n* added initial tests for json output ([22e3166](https://github.com/k8sgpt-ai/k8sgpt/commit/22e31661bff27b28339898826a34ffdcfcff3583))\n* analyzer and ai interfacing ([#200](https://github.com/k8sgpt-ai/k8sgpt/issues/200)) ([0195bfa](https://github.com/k8sgpt-ai/k8sgpt/commit/0195bfab30ab748b3bb7f1b8c8f0e988b99ee54d))\n* **deps:** pin anchore/sbom-action action to 448520c ([#203](https://github.com/k8sgpt-ai/k8sgpt/issues/203)) ([9ff3fbc](https://github.com/k8sgpt-ai/k8sgpt/commit/9ff3fbc382ed78b55a7a1966ecdae186c03b2848))\n* **deps:** update golang docker tag to v1.20.3 ([e9994b8](https://github.com/k8sgpt-ai/k8sgpt/commit/e9994b8d167d4f1d9c0d0dabf8385ff22cfd16a4))\n* made json output prettier and improved output ([db40734](https://github.com/k8sgpt-ai/k8sgpt/commit/db40734a0db89850a2a685c9a7f5f5559875b7b3))\n\n## [0.1.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.7...v0.1.8) (2023-04-03)\n\n\n### Features\n\n* add password flag for backend authentication ([#199](https://github.com/k8sgpt-ai/k8sgpt/issues/199)) ([075a940](https://github.com/k8sgpt-ai/k8sgpt/commit/075a940d2c9bdd8aa9162940ed46abad47d46998))\n* adding shields to readme ([213ecd8](https://github.com/k8sgpt-ai/k8sgpt/commit/213ecd8e83933fabaa5d3d674c67958599dd72ce))\n* adding unit testing and example ([35b838b](https://github.com/k8sgpt-ai/k8sgpt/commit/35b838bfafa248dbf3932c7a3ee708b1a1539f18))\n* alias filter to filters ([dde4e83](https://github.com/k8sgpt-ai/k8sgpt/commit/dde4e833b0e87553dea4e5c1e17a14e303956bc1))\n* analyzer ifacing ([426f562](https://github.com/k8sgpt-ai/k8sgpt/commit/426f562be83ed0e708a07b9e1900ac06fa017c27))\n* service test ([44cc8f7](https://github.com/k8sgpt-ai/k8sgpt/commit/44cc8f7ad68d152ec577e57cab7d8d9ab9613378))\n* test workflow ([5f30a4d](https://github.com/k8sgpt-ai/k8sgpt/commit/5f30a4ddf44ebff949bb0573f261667539a2dcfb))\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.8 ([91fb065](https://github.com/k8sgpt-ai/k8sgpt/commit/91fb06530a21259da6e72c28342e743d2b481294))\n\n\n### Other\n\n* create linux packages ([#201](https://github.com/k8sgpt-ai/k8sgpt/issues/201)) ([67753be](https://github.com/k8sgpt-ai/k8sgpt/commit/67753be6f317c462ebe1d9a316f2b0c9684ca4e5))\n* **deps:** pin dependencies ([#198](https://github.com/k8sgpt-ai/k8sgpt/issues/198)) ([f8291aa](https://github.com/k8sgpt-ai/k8sgpt/commit/f8291aab085209f9fee13a6c92c96076163e2e90))\n\n## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)\n\n\n### Features\n\n* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))\n* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))\n* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))\n\n\n### Bug Fixes\n\n* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))\n* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))\n* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))\n\n\n### Refactoring\n\n* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)\n\n\n### Other\n\n* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))\n* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))\n* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))\n* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))\n* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))\n* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))\n\n\n### Docs\n\n* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))\n\n## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)\n\n\n### Bug Fixes\n\n* analysis detail not displayed when --explain ([869ba90](https://github.com/k8sgpt-ai/k8sgpt/commit/869ba909075a5543413fb6ae7fc79aa067c08da4))\n\n## [0.1.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.4...v0.1.5) (2023-03-31)\n\n\n### Features\n\n* add & remove default filter(s) to analyze. ([32ddf66](https://github.com/k8sgpt-ai/k8sgpt/commit/32ddf6691ce083fd4283a1d5ac4b9f02e90df867))\n* add filter command add \"list\" subcommand ([#159](https://github.com/k8sgpt-ai/k8sgpt/issues/159)) ([6e17c9e](https://github.com/k8sgpt-ai/k8sgpt/commit/6e17c9e285e3871bb8f694b734a8cd6fd02e60f0))\n* check if filters does not empty on add & remove ([975813d](https://github.com/k8sgpt-ai/k8sgpt/commit/975813d3284719c877630ad20f90c6fe163283da))\n* remove filter prefix on subcommand ([30faf84](https://github.com/k8sgpt-ai/k8sgpt/commit/30faf842541c0be6b6483f71f6cf04d5cafecef5))\n* rework filters ([3ed545f](https://github.com/k8sgpt-ai/k8sgpt/commit/3ed545f33fb3ecb3827c03e8c89027c61386c44f))\n* update filters add & remove to be more consistent ([9aa0e89](https://github.com/k8sgpt-ai/k8sgpt/commit/9aa0e8960ee340208b4749954c99867842ba58b9))\n\n\n### Bug Fixes\n\n* kubecontext flag has no effect ([a8bf451](https://github.com/k8sgpt-ai/k8sgpt/commit/a8bf45134ff3a72dc3e531d720f119790faff9d4))\n* spelling on dupplicateFilters ([0a12448](https://github.com/k8sgpt-ai/k8sgpt/commit/0a124484a23789376258413e73628c7b1d7abded))\n\n\n### Other\n\n* renamed filter list file ([25f8dc3](https://github.com/k8sgpt-ai/k8sgpt/commit/25f8dc390cccd66965993f464351e671af11f8ac))\n\n## [0.1.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.3...v0.1.4) (2023-03-30)\n\n\n### Features\n\n* add Ingress class validation ([#154](https://github.com/k8sgpt-ai/k8sgpt/issues/154)) ([b061566](https://github.com/k8sgpt-ai/k8sgpt/commit/b061566404ef80288ca29add2d401574109d44c0))\n* output selected backend ([#153](https://github.com/k8sgpt-ai/k8sgpt/issues/153)) ([be061da](https://github.com/k8sgpt-ai/k8sgpt/commit/be061da5b65045938acd70ad2eb2d21b87d2d6bf))\n\n\n### Bug Fixes\n\n* now supports different kubeconfig and kubectx ([c8f3c94](https://github.com/k8sgpt-ai/k8sgpt/commit/c8f3c946b00c00cd185961a4fa777806da94014e))\n\n\n### Refactoring\n\n* removed sample flag ([0afd528](https://github.com/k8sgpt-ai/k8sgpt/commit/0afd52844b96579391f77698bf0555145b6d2be8))\n\n## [0.1.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.2...v0.1.3) (2023-03-30)\n\n\n### Features\n\n* add secret validation to ingress analyzer ([#141](https://github.com/k8sgpt-ai/k8sgpt/issues/141)) ([86c7e81](https://github.com/k8sgpt-ai/k8sgpt/commit/86c7e81e18db02ebcbfe35d470682c982871375f))\n* bugfix for output ([2eab0c5](https://github.com/k8sgpt-ai/k8sgpt/commit/2eab0c544fbb6026f6aea79b08d8f29c061acf2e))\n* CODE_OF_CONDUCT.md ([#129](https://github.com/k8sgpt-ai/k8sgpt/issues/129)) ([fe73633](https://github.com/k8sgpt-ai/k8sgpt/commit/fe73633273c5c1f4188bca48471283535967d5aa))\n* create-security.md ([27b8916](https://github.com/k8sgpt-ai/k8sgpt/commit/27b8916f297570907437686c6d958636fb249d50))\n* improvement to analysis speed ([548039e](https://github.com/k8sgpt-ai/k8sgpt/commit/548039ebe62bb609c1aa288e5e49845850fd2dd8))\n* init ingress analyzer ([#138](https://github.com/k8sgpt-ai/k8sgpt/issues/138)) ([fe683b7](https://github.com/k8sgpt-ai/k8sgpt/commit/fe683b71b84fe82459b0ffe366b4dcfa1c978cfe))\n\n\n### Bug Fixes\n\n* add Ingress in GetParent switch case ([14ba8d5](https://github.com/k8sgpt-ai/k8sgpt/commit/14ba8d555005f31fc2201cb8b61653093c19b8a7))\n* bugfix for output ([#148](https://github.com/k8sgpt-ai/k8sgpt/issues/148)) ([172c2df](https://github.com/k8sgpt-ai/k8sgpt/commit/172c2df6c55f5fddbfec7f8526be5f2323d1b900))\n* Change ObjectMeta value in Ingress analyser. ([bf49a51](https://github.com/k8sgpt-ai/k8sgpt/commit/bf49a51c62af450cff51a590547ef30989bd2e93))\n* typo in description of the filter flag in analyze command ([#147](https://github.com/k8sgpt-ai/k8sgpt/issues/147)) ([f4765be](https://github.com/k8sgpt-ai/k8sgpt/commit/f4765bed1b1ad121a81b35878fdb866354b5e34a))\n\n\n### Other\n\n* **deps:** update google-github-actions/release-please-action digest to ee9822e ([#132](https://github.com/k8sgpt-ai/k8sgpt/issues/132)) ([01b2826](https://github.com/k8sgpt-ai/k8sgpt/commit/01b282647512a4eaebd42ab5847b5534de148d14))\n\n\n### Docs\n\n* add new slack link ([#134](https://github.com/k8sgpt-ai/k8sgpt/issues/134)) ([#135](https://github.com/k8sgpt-ai/k8sgpt/issues/135)) ([cad2b38](https://github.com/k8sgpt-ai/k8sgpt/commit/cad2b38d037658495024ec0166ebd3e936f65c2e))\n\n## [0.1.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.1...v0.1.2) (2023-03-28)\n\n\n### Features\n\n* added namespace filter ([#127](https://github.com/k8sgpt-ai/k8sgpt/issues/127)) ([b78ab3d](https://github.com/k8sgpt-ai/k8sgpt/commit/b78ab3d9b503a256bf6ccf18276e20140ae17d1c))\n* prefix templates ([#125](https://github.com/k8sgpt-ai/k8sgpt/issues/125)) ([65a568e](https://github.com/k8sgpt-ai/k8sgpt/commit/65a568e937a8fdacc179f5e8b1a021a0178c04f0))\n\n\n### Bug Fixes\n\n* readme code blocks ([#126](https://github.com/k8sgpt-ai/k8sgpt/issues/126)) ([c8b92aa](https://github.com/k8sgpt-ai/k8sgpt/commit/c8b92aaa0e2795aa8d65f84277c8adfe0f1d14e3))\n* update README.md ([#119](https://github.com/k8sgpt-ai/k8sgpt/issues/119)) ([05abe97](https://github.com/k8sgpt-ai/k8sgpt/commit/05abe975dd859cd85096a1a7182f17b0437ad20f))\n\n\n### Other\n\n* added default issue template ([#96](https://github.com/k8sgpt-ai/k8sgpt/issues/96)) ([#121](https://github.com/k8sgpt-ai/k8sgpt/issues/121)) ([11c227b](https://github.com/k8sgpt-ai/k8sgpt/commit/11c227b82e16dac8b46cbd03bb04d9cc1c2b5ac3))\n\n\n### Docs\n\n* add new issue templates ([dbd305f](https://github.com/k8sgpt-ai/k8sgpt/commit/dbd305f901cca09b7148254c3aa7a7435504d6cc))\n* add WSL gcc instructions ([4d5566b](https://github.com/k8sgpt-ai/k8sgpt/commit/4d5566b4df7aedf43edbeeb03130f0ba77dbed1a))\n* added Windows and Linux instalation steps in README ([#116](https://github.com/k8sgpt-ai/k8sgpt/issues/116)) ([3bfb278](https://github.com/k8sgpt-ai/k8sgpt/commit/3bfb278f81a9c550ee37a88c0cb0377331802542))\n* fix indentations ([a46416d](https://github.com/k8sgpt-ai/k8sgpt/commit/a46416dce0f5cee2d42b27525023b04af1a8e3c0))\n* rename ISSUE_TEMPLATE ([#124](https://github.com/k8sgpt-ai/k8sgpt/issues/124)) ([cb4932c](https://github.com/k8sgpt-ai/k8sgpt/commit/cb4932c39df4903a4b48ae5f0428860027f76fd2))\n\n## [0.1.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.0...v0.1.1) (2023-03-28)\n\n\n### Features\n\n* this stops service exiting the program ([6f90386](https://github.com/k8sgpt-ai/k8sgpt/commit/6f90386fc93b2e39e59832468922e8ba7210b8e5))\n* updated readme ([e0141d1](https://github.com/k8sgpt-ai/k8sgpt/commit/e0141d1cf54b5b37b25a5caeb9d5c940b9410ea7))\n\n\n### Bug Fixes\n\n* short term solution for exhaustion ([5890e3a](https://github.com/k8sgpt-ai/k8sgpt/commit/5890e3a79c80a2973af2feb7d50e7f9c57c563c2))\n\n\n### Other\n\n* update README.md ([93b947f](https://github.com/k8sgpt-ai/k8sgpt/commit/93b947f261e401c10dde6dc1854e6e22187437d6))\n* update root.go path ([2cb1c9c](https://github.com/k8sgpt-ai/k8sgpt/commit/2cb1c9c150d052bb3942d9f62ded9d54b0e1873e))\n\n## [0.1.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.9...v0.1.0) (2023-03-28)\n\n\n### Features\n\n* added british alias ([39c0444](https://github.com/k8sgpt-ai/k8sgpt/commit/39c0444fac9b46d0faa347b45df779b97019e5b6)), closes [#93](https://github.com/k8sgpt-ai/k8sgpt/issues/93)\n* enables overwriting of cache ([#95](https://github.com/k8sgpt-ai/k8sgpt/issues/95)) ([a270f7c](https://github.com/k8sgpt-ai/k8sgpt/commit/a270f7c89fb8bec35984715c5e4d160a2307e678))\n\n\n### Other\n\n* add CODEOWNERS ([c5c6162](https://github.com/k8sgpt-ai/k8sgpt/commit/c5c6162df1f3701659e47bce6e9fc6e3c569e539))\n* add codeowners file ([#102](https://github.com/k8sgpt-ai/k8sgpt/issues/102)) ([829ff56](https://github.com/k8sgpt-ai/k8sgpt/commit/829ff566c0a964250d3d8d45306d410e1b9d9d35))\n* release 0.1.0 ([f9c7daf](https://github.com/k8sgpt-ai/k8sgpt/commit/f9c7daf3dcd06dcd9cea5603108b8a42ee273348))\n\n## [0.0.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.8...v0.0.9) (2023-03-28)\n\n\n### Other\n\n* small update ([202e8e2](https://github.com/k8sgpt-ai/k8sgpt/commit/202e8e2977422b2b4506a80dc9b76a392c5457eb))\n\n## [0.0.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.7...v0.0.8) (2023-03-27)\n\n\n### Features\n\n* add generation of api-keys to cli ([#87](https://github.com/k8sgpt-ai/k8sgpt/issues/87)) ([1c653ec](https://github.com/k8sgpt-ai/k8sgpt/commit/1c653ecc51b74a2f51ce7240ffaee0fe75f2e8dd))\n* add generation of api-keys to cli ([#87](https://github.com/k8sgpt-ai/k8sgpt/issues/87)) ([bb2db5c](https://github.com/k8sgpt-ai/k8sgpt/commit/bb2db5ca7923e2049308d1674bb59ae8154e415c))\n* addition of simple language support ([c3008c5](https://github.com/k8sgpt-ai/k8sgpt/commit/c3008c5e75acbb35d864135199ca9c034f59e35f))\n* version ([0c231d6](https://github.com/k8sgpt-ai/k8sgpt/commit/0c231d635e7ad71609bb80abac5e0ade15ffb860))\n* version ([931f072](https://github.com/k8sgpt-ai/k8sgpt/commit/931f072e0ab0cfd77f261b0b719cf0819f85b951))\n\n## [0.0.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.6...v0.0.7) (2023-03-27)\n\n\n### Features\n\n* wip fixing missing details ([0852c65](https://github.com/k8sgpt-ai/k8sgpt/commit/0852c658ded33b91e1d323bd8cba6ac6935cb525))\n\n\n### Other\n\n* moved code ([a194d4a](https://github.com/k8sgpt-ai/k8sgpt/commit/a194d4a509329cbc5a00724b0a19c75726c2a0d3))\n* return success on no issues ([009f47c](https://github.com/k8sgpt-ai/k8sgpt/commit/009f47c8e8ee6d3ce9b36110c36edae97690c949))\n* updated readme ([06fb807](https://github.com/k8sgpt-ai/k8sgpt/commit/06fb8073dc5b0b5bd9f8d115d9ec206ab238d68f))\n\n## [0.0.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.6...v0.0.6) (2023-03-26)\n\n\n### Features\n\n* add service analysis ([961fb6c](https://github.com/k8sgpt-ai/k8sgpt/commit/961fb6c555f59f1276531f462739b76b1508830e))\n* added analysis for pvcs ([88d49ae](https://github.com/k8sgpt-ai/k8sgpt/commit/88d49ae21c7d889d59361de157360f80503683be))\n* also fixes bug if the events feed is empty ([#73](https://github.com/k8sgpt-ai/k8sgpt/issues/73)) ([a1093dc](https://github.com/k8sgpt-ai/k8sgpt/commit/a1093dcfe468a7671c9e543372f73780fb38418e))\n* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))\n* find parent objects ([b29c6e4](https://github.com/k8sgpt-ai/k8sgpt/commit/b29c6e45825807d07dd6fdb954457772f40b1b0e))\n* find parent objects and add information about them ([#72](https://github.com/k8sgpt-ai/k8sgpt/issues/72)) ([14e85b0](https://github.com/k8sgpt-ai/k8sgpt/commit/14e85b08ff7d9a571796905260db7f1056b6e838))\n* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))\n* initial json implementation ([#68](https://github.com/k8sgpt-ai/k8sgpt/issues/68)) ([979f13f](https://github.com/k8sgpt-ai/k8sgpt/commit/979f13f043f54a5bc74d0a49fee0db2faaf0a4f8))\n* interfaced out ai clients ([90b3c08](https://github.com/k8sgpt-ai/k8sgpt/commit/90b3c0898c8ab1299ce8b60effe981f5fc9ed63b))\n* support for multi-auth ([51aa59a](https://github.com/k8sgpt-ai/k8sgpt/commit/51aa59aea8c0fd5533d2300c7a79c0b9008ef887))\n* updated readme ([7336924](https://github.com/k8sgpt-ai/k8sgpt/commit/73369240b4fc8c91dae0ae272e671f7b413e3bdc))\n\n\n### Bug Fixes\n\n* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))\n* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))\n* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.5 ([105fe44](https://github.com/k8sgpt-ai/k8sgpt/commit/105fe44680e5a987d4a65ff9c58b5b2211808c5e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.7 ([7f7726d](https://github.com/k8sgpt-ai/k8sgpt/commit/7f7726d59a63baeaf8ff110e00b30a20ec7f1df5))\n* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))\n* missing parent when explain is used ([9c7d559](https://github.com/k8sgpt-ai/k8sgpt/commit/9c7d55955b777ad201307cb946e0fc81cf9c4b99))\n* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))\n* remove sboms from goreleaser ([addc01f](https://github.com/k8sgpt-ai/k8sgpt/commit/addc01f700dd2ea31ec24dcf4995bb7ed4a4785e))\n* semantic commit token permission ([#69](https://github.com/k8sgpt-ai/k8sgpt/issues/69)) ([0181c0a](https://github.com/k8sgpt-ai/k8sgpt/commit/0181c0aeb56ad82fd232ce1c7788c43b7bd03bf2))\n\n\n### Docs\n\n* add some important information to contributing ([9ab7f58](https://github.com/k8sgpt-ai/k8sgpt/commit/9ab7f587620d69e4e8fc98faabce6417c35f7497))\n* update CONTRIBUTING ([05a787d](https://github.com/k8sgpt-ai/k8sgpt/commit/05a787d53dfe5e625c6449ac1e21ec36e66ddd28))\n* update CONTRIBUTING ([26449e1](https://github.com/k8sgpt-ai/k8sgpt/commit/26449e10efd8926cccd4a2eaa4e9dc3afa8bd01a))\n\n\n### Other\n\n* add bot secret to goreleaser ([171e58b](https://github.com/k8sgpt-ai/k8sgpt/commit/171e58b51107f75717694e35c4e249ee41f0409a))\n* add brew tap generation on release ([2992c4e](https://github.com/k8sgpt-ai/k8sgpt/commit/2992c4e5c8abad50c90ed85523c732f19ab1f31c))\n* add initial renovate config ([e37dbc7](https://github.com/k8sgpt-ai/k8sgpt/commit/e37dbc7909f1c520c4c6660c25b45de5847ea581))\n* add pull request template ([a6d5132](https://github.com/k8sgpt-ai/k8sgpt/commit/a6d5132b8c2ff077680e2edfd8361a93008197fd))\n* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))\n* add semantic pr validation ([#66](https://github.com/k8sgpt-ai/k8sgpt/issues/66)) ([ad594c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ad594c7cb2105e0eff72d1767b2ddcc4dc0e3d38))\n* change module repo ([a307c13](https://github.com/k8sgpt-ai/k8sgpt/commit/a307c132b3464ff2e949c8a5588e01d344de91a0))\n* **deps:** pin amannn/action-semantic-pull-request action to c3cd5d1 ([3621766](https://github.com/k8sgpt-ai/k8sgpt/commit/36217667ceb87d9b97b44dc91e0ff6e7a1b86e14))\n* **deps:** pin dependencies ([f6072f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f6072f56cbe2c073b7b7ebef6c12fa98120e54e2))\n* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))\n* **deps:** pin dependencies ([7fea7d1](https://github.com/k8sgpt-ai/k8sgpt/commit/7fea7d14a572fe0fd05f5f241b98e93655fb1965))\n* **deps:** update actions/checkout digest to 8f4b7f8 ([9955d75](https://github.com/k8sgpt-ai/k8sgpt/commit/9955d754505b60f28d17397132a1d02e95ffe303))\n* **main:** release 0.0.3 ([53c9947](https://github.com/k8sgpt-ai/k8sgpt/commit/53c994725ea2c2c54898ffe5307d9df40e9c1fe5))\n* **main:** release 0.0.3 ([f5d8609](https://github.com/k8sgpt-ai/k8sgpt/commit/f5d86092f49faef8d71cb950986d76c3f92daf46))\n* **main:** release 0.0.3 ([22873a6](https://github.com/k8sgpt-ai/k8sgpt/commit/22873a67163e58484d2a0ad343b4ba3c83e51d8f))\n* **main:** release 0.0.4 ([13b7d58](https://github.com/k8sgpt-ai/k8sgpt/commit/13b7d58e590078f086a0af2f9d1800e0e65a28bb))\n* **main:** release 0.0.4 ([aef7256](https://github.com/k8sgpt-ai/k8sgpt/commit/aef7256dc3a85817573744f8b4a54f834368bac7))\n* **main:** release 0.0.4 ([6dbcde9](https://github.com/k8sgpt-ai/k8sgpt/commit/6dbcde94e961a6e5a1fc0559d2a1da5567a659de))\n* **main:** release 0.0.5 ([9fecc1e](https://github.com/k8sgpt-ai/k8sgpt/commit/9fecc1ea6df4104412fc1230372de6f26aa1ade2))\n* **main:** release 0.0.6 ([d554bba](https://github.com/k8sgpt-ai/k8sgpt/commit/d554bba38494745f83b5a8931f665429af35a31a))\n* release 0.0.3 ([4840aa0](https://github.com/k8sgpt-ai/k8sgpt/commit/4840aa081e3aa4a7a01fd3fd5f837fa6f0c3c02c))\n* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))\n* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))\n* release 0.0.4 ([08f2c31](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2c3112e2cc16b49b9cf8fdbd97368acecc754))\n* release 0.0.5 ([8da8945](https://github.com/k8sgpt-ai/k8sgpt/commit/8da8945d1b8d898440be235f88bdb2c08b0f9f84))\n* release 0.0.6 ([dc2bfa9](https://github.com/k8sgpt-ai/k8sgpt/commit/dc2bfa918c080a6c1b2e5ef66d699d9e08e28e10))\n\n## [0.0.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.5...v0.0.6) (2023-03-26)\n\n\n### Features\n\n* add service analysis ([961fb6c](https://github.com/k8sgpt-ai/k8sgpt/commit/961fb6c555f59f1276531f462739b76b1508830e))\n* added analysis for pvcs ([88d49ae](https://github.com/k8sgpt-ai/k8sgpt/commit/88d49ae21c7d889d59361de157360f80503683be))\n* also fixes bug if the events feed is empty ([#73](https://github.com/k8sgpt-ai/k8sgpt/issues/73)) ([a1093dc](https://github.com/k8sgpt-ai/k8sgpt/commit/a1093dcfe468a7671c9e543372f73780fb38418e))\n* find parent objects ([b29c6e4](https://github.com/k8sgpt-ai/k8sgpt/commit/b29c6e45825807d07dd6fdb954457772f40b1b0e))\n* find parent objects and add information about them ([#72](https://github.com/k8sgpt-ai/k8sgpt/issues/72)) ([14e85b0](https://github.com/k8sgpt-ai/k8sgpt/commit/14e85b08ff7d9a571796905260db7f1056b6e838))\n* initial json implementation ([#68](https://github.com/k8sgpt-ai/k8sgpt/issues/68)) ([979f13f](https://github.com/k8sgpt-ai/k8sgpt/commit/979f13f043f54a5bc74d0a49fee0db2faaf0a4f8))\n* interfaced out ai clients ([90b3c08](https://github.com/k8sgpt-ai/k8sgpt/commit/90b3c0898c8ab1299ce8b60effe981f5fc9ed63b))\n* support for multi-auth ([51aa59a](https://github.com/k8sgpt-ai/k8sgpt/commit/51aa59aea8c0fd5533d2300c7a79c0b9008ef887))\n\n\n### Bug Fixes\n\n* missing parent when explain is used ([9c7d559](https://github.com/k8sgpt-ai/k8sgpt/commit/9c7d55955b777ad201307cb946e0fc81cf9c4b99))\n* semantic commit token permission ([#69](https://github.com/k8sgpt-ai/k8sgpt/issues/69)) ([0181c0a](https://github.com/k8sgpt-ai/k8sgpt/commit/0181c0aeb56ad82fd232ce1c7788c43b7bd03bf2))\n\n\n### Other\n\n* add semantic pr validation ([#66](https://github.com/k8sgpt-ai/k8sgpt/issues/66)) ([ad594c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ad594c7cb2105e0eff72d1767b2ddcc4dc0e3d38))\n* **deps:** pin amannn/action-semantic-pull-request action to c3cd5d1 ([3621766](https://github.com/k8sgpt-ai/k8sgpt/commit/36217667ceb87d9b97b44dc91e0ff6e7a1b86e14))\n\n## [0.0.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.4...v0.0.5) (2023-03-24)\n\n\n### Other\n\n* release 0.0.5 ([8da8945](https://github.com/k8sgpt-ai/k8sgpt/commit/8da8945d1b8d898440be235f88bdb2c08b0f9f84))\n\n## [0.0.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.4...v0.0.4) (2023-03-24)\n\n\n### Features\n\n* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))\n* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))\n\n\n### Bug Fixes\n\n* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))\n* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))\n* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.5 ([105fe44](https://github.com/k8sgpt-ai/k8sgpt/commit/105fe44680e5a987d4a65ff9c58b5b2211808c5e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.7 ([7f7726d](https://github.com/k8sgpt-ai/k8sgpt/commit/7f7726d59a63baeaf8ff110e00b30a20ec7f1df5))\n* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))\n* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))\n* remove sboms from goreleaser ([addc01f](https://github.com/k8sgpt-ai/k8sgpt/commit/addc01f700dd2ea31ec24dcf4995bb7ed4a4785e))\n\n\n### Docs\n\n* add some important information to contributing ([9ab7f58](https://github.com/k8sgpt-ai/k8sgpt/commit/9ab7f587620d69e4e8fc98faabce6417c35f7497))\n* update CONTRIBUTING ([05a787d](https://github.com/k8sgpt-ai/k8sgpt/commit/05a787d53dfe5e625c6449ac1e21ec36e66ddd28))\n* update CONTRIBUTING ([26449e1](https://github.com/k8sgpt-ai/k8sgpt/commit/26449e10efd8926cccd4a2eaa4e9dc3afa8bd01a))\n\n\n### Other\n\n* add bot secret to goreleaser ([171e58b](https://github.com/k8sgpt-ai/k8sgpt/commit/171e58b51107f75717694e35c4e249ee41f0409a))\n* add brew tap generation on release ([2992c4e](https://github.com/k8sgpt-ai/k8sgpt/commit/2992c4e5c8abad50c90ed85523c732f19ab1f31c))\n* add initial renovate config ([e37dbc7](https://github.com/k8sgpt-ai/k8sgpt/commit/e37dbc7909f1c520c4c6660c25b45de5847ea581))\n* add pull request template ([a6d5132](https://github.com/k8sgpt-ai/k8sgpt/commit/a6d5132b8c2ff077680e2edfd8361a93008197fd))\n* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))\n* change module repo ([a307c13](https://github.com/k8sgpt-ai/k8sgpt/commit/a307c132b3464ff2e949c8a5588e01d344de91a0))\n* **deps:** pin dependencies ([f6072f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f6072f56cbe2c073b7b7ebef6c12fa98120e54e2))\n* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))\n* **deps:** pin dependencies ([7fea7d1](https://github.com/k8sgpt-ai/k8sgpt/commit/7fea7d14a572fe0fd05f5f241b98e93655fb1965))\n* **deps:** update actions/checkout digest to 8f4b7f8 ([9955d75](https://github.com/k8sgpt-ai/k8sgpt/commit/9955d754505b60f28d17397132a1d02e95ffe303))\n* **main:** release 0.0.3 ([53c9947](https://github.com/k8sgpt-ai/k8sgpt/commit/53c994725ea2c2c54898ffe5307d9df40e9c1fe5))\n* **main:** release 0.0.3 ([f5d8609](https://github.com/k8sgpt-ai/k8sgpt/commit/f5d86092f49faef8d71cb950986d76c3f92daf46))\n* **main:** release 0.0.3 ([22873a6](https://github.com/k8sgpt-ai/k8sgpt/commit/22873a67163e58484d2a0ad343b4ba3c83e51d8f))\n* **main:** release 0.0.4 ([aef7256](https://github.com/k8sgpt-ai/k8sgpt/commit/aef7256dc3a85817573744f8b4a54f834368bac7))\n* **main:** release 0.0.4 ([6dbcde9](https://github.com/k8sgpt-ai/k8sgpt/commit/6dbcde94e961a6e5a1fc0559d2a1da5567a659de))\n* release 0.0.3 ([4840aa0](https://github.com/k8sgpt-ai/k8sgpt/commit/4840aa081e3aa4a7a01fd3fd5f837fa6f0c3c02c))\n* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))\n* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))\n* release 0.0.4 ([08f2c31](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2c3112e2cc16b49b9cf8fdbd97368acecc754))\n\n## [0.0.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.4...v0.0.4) (2023-03-24)\n\n\n### Features\n\n* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))\n* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))\n\n\n### Bug Fixes\n\n* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))\n* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))\n* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.5 ([105fe44](https://github.com/k8sgpt-ai/k8sgpt/commit/105fe44680e5a987d4a65ff9c58b5b2211808c5e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.7 ([7f7726d](https://github.com/k8sgpt-ai/k8sgpt/commit/7f7726d59a63baeaf8ff110e00b30a20ec7f1df5))\n* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))\n* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))\n* remove sboms from goreleaser ([addc01f](https://github.com/k8sgpt-ai/k8sgpt/commit/addc01f700dd2ea31ec24dcf4995bb7ed4a4785e))\n\n\n### Docs\n\n* add some important information to contributing ([9ab7f58](https://github.com/k8sgpt-ai/k8sgpt/commit/9ab7f587620d69e4e8fc98faabce6417c35f7497))\n* update CONTRIBUTING ([05a787d](https://github.com/k8sgpt-ai/k8sgpt/commit/05a787d53dfe5e625c6449ac1e21ec36e66ddd28))\n* update CONTRIBUTING ([26449e1](https://github.com/k8sgpt-ai/k8sgpt/commit/26449e10efd8926cccd4a2eaa4e9dc3afa8bd01a))\n\n\n### Other\n\n* add bot secret to goreleaser ([171e58b](https://github.com/k8sgpt-ai/k8sgpt/commit/171e58b51107f75717694e35c4e249ee41f0409a))\n* add brew tap generation on release ([2992c4e](https://github.com/k8sgpt-ai/k8sgpt/commit/2992c4e5c8abad50c90ed85523c732f19ab1f31c))\n* add initial renovate config ([e37dbc7](https://github.com/k8sgpt-ai/k8sgpt/commit/e37dbc7909f1c520c4c6660c25b45de5847ea581))\n* add pull request template ([a6d5132](https://github.com/k8sgpt-ai/k8sgpt/commit/a6d5132b8c2ff077680e2edfd8361a93008197fd))\n* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))\n* change module repo ([a307c13](https://github.com/k8sgpt-ai/k8sgpt/commit/a307c132b3464ff2e949c8a5588e01d344de91a0))\n* **deps:** pin dependencies ([f6072f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f6072f56cbe2c073b7b7ebef6c12fa98120e54e2))\n* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))\n* **deps:** pin dependencies ([7fea7d1](https://github.com/k8sgpt-ai/k8sgpt/commit/7fea7d14a572fe0fd05f5f241b98e93655fb1965))\n* **deps:** update actions/checkout digest to 8f4b7f8 ([9955d75](https://github.com/k8sgpt-ai/k8sgpt/commit/9955d754505b60f28d17397132a1d02e95ffe303))\n* **main:** release 0.0.3 ([53c9947](https://github.com/k8sgpt-ai/k8sgpt/commit/53c994725ea2c2c54898ffe5307d9df40e9c1fe5))\n* **main:** release 0.0.3 ([f5d8609](https://github.com/k8sgpt-ai/k8sgpt/commit/f5d86092f49faef8d71cb950986d76c3f92daf46))\n* **main:** release 0.0.3 ([22873a6](https://github.com/k8sgpt-ai/k8sgpt/commit/22873a67163e58484d2a0ad343b4ba3c83e51d8f))\n* **main:** release 0.0.4 ([6dbcde9](https://github.com/k8sgpt-ai/k8sgpt/commit/6dbcde94e961a6e5a1fc0559d2a1da5567a659de))\n* release 0.0.3 ([4840aa0](https://github.com/k8sgpt-ai/k8sgpt/commit/4840aa081e3aa4a7a01fd3fd5f837fa6f0c3c02c))\n* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))\n* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))\n* release 0.0.4 ([08f2c31](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2c3112e2cc16b49b9cf8fdbd97368acecc754))\n\n## [0.0.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.3...v0.0.4) (2023-03-24)\n\n\n### Bug Fixes\n\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.7 ([7f7726d](https://github.com/k8sgpt-ai/k8sgpt/commit/7f7726d59a63baeaf8ff110e00b30a20ec7f1df5))\n\n\n### Docs\n\n* add some important information to contributing ([9ab7f58](https://github.com/k8sgpt-ai/k8sgpt/commit/9ab7f587620d69e4e8fc98faabce6417c35f7497))\n* update CONTRIBUTING ([05a787d](https://github.com/k8sgpt-ai/k8sgpt/commit/05a787d53dfe5e625c6449ac1e21ec36e66ddd28))\n* update CONTRIBUTING ([26449e1](https://github.com/k8sgpt-ai/k8sgpt/commit/26449e10efd8926cccd4a2eaa4e9dc3afa8bd01a))\n\n\n### Other\n\n* add bot secret to goreleaser ([171e58b](https://github.com/k8sgpt-ai/k8sgpt/commit/171e58b51107f75717694e35c4e249ee41f0409a))\n* add brew tap generation on release ([2992c4e](https://github.com/k8sgpt-ai/k8sgpt/commit/2992c4e5c8abad50c90ed85523c732f19ab1f31c))\n* **deps:** update actions/checkout digest to 8f4b7f8 ([9955d75](https://github.com/k8sgpt-ai/k8sgpt/commit/9955d754505b60f28d17397132a1d02e95ffe303))\n\n## [0.0.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.3...v0.0.3) (2023-03-23)\n\n\n### Features\n\n* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))\n* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))\n\n\n### Bug Fixes\n\n* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))\n* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))\n* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.5 ([105fe44](https://github.com/k8sgpt-ai/k8sgpt/commit/105fe44680e5a987d4a65ff9c58b5b2211808c5e))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))\n* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))\n* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))\n\n\n### Other\n\n* add initial renovate config ([e37dbc7](https://github.com/k8sgpt-ai/k8sgpt/commit/e37dbc7909f1c520c4c6660c25b45de5847ea581))\n* add pull request template ([a6d5132](https://github.com/k8sgpt-ai/k8sgpt/commit/a6d5132b8c2ff077680e2edfd8361a93008197fd))\n* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))\n* change module repo ([a307c13](https://github.com/k8sgpt-ai/k8sgpt/commit/a307c132b3464ff2e949c8a5588e01d344de91a0))\n* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))\n* **deps:** pin dependencies ([7fea7d1](https://github.com/k8sgpt-ai/k8sgpt/commit/7fea7d14a572fe0fd05f5f241b98e93655fb1965))\n* **main:** release 0.0.3 ([f5d8609](https://github.com/k8sgpt-ai/k8sgpt/commit/f5d86092f49faef8d71cb950986d76c3f92daf46))\n* **main:** release 0.0.3 ([22873a6](https://github.com/k8sgpt-ai/k8sgpt/commit/22873a67163e58484d2a0ad343b4ba3c83e51d8f))\n* release 0.0.3 ([4840aa0](https://github.com/k8sgpt-ai/k8sgpt/commit/4840aa081e3aa4a7a01fd3fd5f837fa6f0c3c02c))\n* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))\n* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))\n\n## [0.0.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.3...v0.0.3) (2023-03-23)\n\n\n### Other\n\n* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))\n* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))\n\n## [0.0.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.2...v0.0.3) (2023-03-23)\n\n\n### Features\n\n* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))\n* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))\n\n\n### Bug Fixes\n\n* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))\n* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))\n* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))\n* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))\n* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))\n* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))\n\n\n### Other\n\n* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))\n* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ncontact@k8sgpt.ai.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\nWe're happy that you want to contribute to this project. Please read the sections to make the process as smooth as possible.\n\n## Requirements\n- Golang `1.24+`\n- An OpenAI API key\n  * OpenAI API keys can be obtained from [OpenAI](https://platform.openai.com/account/api-keys)\n  * You can set the API key for k8sgpt using `./k8sgpt auth key`\n- If you want to build the container image, you need to have a container engine (docker, podman, rancher, etc.) installed\n\n## Getting Started\n\n**Where should I start?**\n- If you are new to the project, please check out the [good first issue](https://github.com/k8sgpt-ai/k8sgpt/labels/good%20first%20issue) label.\n- If you are looking for something to work on, check out our [open issues](https://github.com/k8sgpt-ai/k8sgpt/issues).\n- If you have an idea for a new feature, please open an issue, and we can discuss it.\n- We are also happy to help you find something to work on. Just reach out to us.\n\n**Getting in touch with the community**\n* Join our [#k8sgpt slack channel](https://join.slack.com/t/k8sgpt/shared_invite/zt-1rwe5fpzq-VNtJK8DmYbbm~iWL1H34nw)\n* Introduce yourself on the slack channel or open an issue to let us know that you are interested in contributing\n\n**Discuss issues**\n* Before you start working on something, propose and discuss your solution on the issue\n* If you are unsure about something, ask the community\n\n**How do I contribute?**\n- Fork the repository and clone it locally\n- Create a new branch and follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines for work undertaken\n- Assign yourself to the issue, if you are working on it (if you are not a member of the organization, please leave a comment on the issue)\n- Make your changes\n- Keep pull requests small and focused, if you have multiple changes, please open multiple PRs\n- Create a pull request back to the upstream repository and follow the [pull request template](.github/pull_request_template.md) guidelines.\n- Wait for a review and address any comments\n\n**Opening PRs**\n- As long as you are working on your PR, please mark it as a draft\n- Please make sure that your PR is up-to-date with the latest changes in `main`\n- Fill out the PR template\n- Mention the issue that your PR is addressing (closes: #<id>)\n- Make sure that your PR passes all checks\n\n**Reviewing PRs**\n- Be respectful and constructive\n- Assign yourself to the PR\n- Check if all checks are passing\n- Suggest changes instead of simply commenting on found issues\n- If you are unsure about something, ask the author\n- If you are not sure if the changes work, try them out\n- Reach out to other reviewers if you are unsure about something\n- If you are happy with the changes, approve the PR\n- Merge the PR once it has all approvals and the checks are passing\n\n## DCO\nWe have a DCO check which runs on every PR to verify that the commit has been signed off.\n\nTo sign off the last commit you made, you can use\n\n```\ngit commit --amend --signoff\n```\n\nYou can also automate signing off your commits by adding the following to your `.zshrc` or `.bashrc`:\n\n```\ngit() {\n  if [ $# -gt 0 ] && [[ \"$1\" == \"commit\" ]] ; then\n     shift\n     command git commit --signoff \"$@\"\n  else\n     command git \"$@\"\n  fi\n}\n```\n\n## Semantic commits\nWe use [Semantic Commits](https://www.conventionalcommits.org/en/v1.0.0/) to make it easier to understand what a commit does and to build pretty changelogs. Please use the following prefixes for your commits:\n- `feat`: A new feature\n- `fix`: A bug fix\n- `docs`: Documentation changes\n- `chores`: Changes to the build process or auxiliary tools and libraries such as documentation generation\n- `refactor`: A code change that neither fixes a bug nor adds a feature\n- `test`: Adding missing tests or correcting existing tests\n- `ci`: Changes to our CI configuration files and scripts\n\nAn example for this could be:\n```\ngit commit -m \"docs: add a new section to the README\"\n```\n\n## Building\nBuilding the binary is as simple as running `go build .` in the root of the repository. If you want to build the container image, you can run `docker build -t k8sgpt -f container/Dockerfile .` in the root of the repository.\n\n## Releasing\nReleases of k8sgpt are done using [Release Please](https://github.com/googleapis/release-please) and [GoReleaser](https://goreleaser.com/). The workflow looks like this:\n\n* A PR is merged to the `main` branch:\n  * Release please is triggered, creates or updates a new release PR\n  * This is done with every merge to main, the current release PR is updated every time\n\n* Merging the 'release please' PR to `main`:\n  * Release please is triggered, creates a new release and updates the changelog based on the commit messages\n  * GoReleaser is triggered, builds the binaries and attaches them to the release\n  * Containers are created and pushed to the container registry\n\n> With the next relevant merge, a new release PR will be created and the process starts again\n\n### Manually setting the version\nIf you want to manually set the version, you can create a PR with an empty commit message that contains the version number in the commit message. For example:\n\nSuch a commit can get produced as follows: `git commit --allow-empty -m \"chore: release 0.0.3\" -m \"Release-As: 0.0.3`\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n                        http://www.apache.org/licenses/\r\n\r\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n   1. Definitions.\r\n\r\n      \"License\" shall mean the terms and conditions for use, reproduction,\r\n      and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n      \"Licensor\" shall mean the copyright owner or entity authorized by\r\n      the copyright owner that is granting the License.\r\n\r\n      \"Legal Entity\" shall mean the union of the acting entity and all\r\n      other entities that control, are controlled by, or are under common\r\n      control with that entity. For the purposes of this definition,\r\n      \"control\" means (i) the power, direct or indirect, to cause the\r\n      direction or management of such entity, whether by contract or\r\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n      outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n      exercising permissions granted by this License.\r\n\r\n      \"Source\" form shall mean the preferred form for making modifications,\r\n      including but not limited to software source code, documentation\r\n      source, and configuration files.\r\n\r\n      \"Object\" form shall mean any form resulting from mechanical\r\n      transformation or translation of a Source form, including but\r\n      not limited to compiled object code, generated documentation,\r\n      and conversions to other media types.\r\n\r\n      \"Work\" shall mean the work of authorship, whether in Source or\r\n      Object form, made available under the License, as indicated by a\r\n      copyright notice that is included in or attached to the work\r\n      (an example is provided in the Appendix below).\r\n\r\n      \"Derivative Works\" shall mean any work, whether in Source or Object\r\n      form, that is based on (or derived from) the Work and for which the\r\n      editorial revisions, annotations, elaborations, or other modifications\r\n      represent, as a whole, an original work of authorship. For the purposes\r\n      of this License, Derivative Works shall not include works that remain\r\n      separable from, or merely link (or bind by name) to the interfaces of,\r\n      the Work and Derivative Works thereof.\r\n\r\n      \"Contribution\" shall mean any work of authorship, including\r\n      the original version of the Work and any modifications or additions\r\n      to that Work or Derivative Works thereof, that is intentionally\r\n      submitted to Licensor for inclusion in the Work by the copyright owner\r\n      or by an individual or Legal Entity authorized to submit on behalf of\r\n      the copyright owner. For the purposes of this definition, \"submitted\"\r\n      means any form of electronic, verbal, or written communication sent\r\n      to the Licensor or its representatives, including but not limited to\r\n      communication on electronic mailing lists, source code control systems,\r\n      and issue tracking systems that are managed by, or on behalf of, the\r\n      Licensor for the purpose of discussing and improving the Work, but\r\n      excluding communication that is conspicuously marked or otherwise\r\n      designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n      on behalf of whom a Contribution has been received by Licensor and\r\n      subsequently incorporated within the Work.\r\n\r\n   2. Grant of Copyright License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      copyright license to reproduce, prepare Derivative Works of,\r\n      publicly display, publicly perform, sublicense, and distribute the\r\n      Work and such Derivative Works in Source or Object form.\r\n\r\n   3. Grant of Patent License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      (except as stated in this section) patent license to make, have made,\r\n      use, offer to sell, sell, import, and otherwise transfer the Work,\r\n      where such license applies only to those patent claims licensable\r\n      by such Contributor that are necessarily infringed by their\r\n      Contribution(s) alone or by combination of their Contribution(s)\r\n      with the Work to which such Contribution(s) was submitted. If You\r\n      institute patent litigation against any entity (including a\r\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n      or a Contribution incorporated within the Work constitutes direct\r\n      or contributory patent infringement, then any patent licenses\r\n      granted to You under this License for that Work shall terminate\r\n      as of the date such litigation is filed.\r\n\r\n   4. Redistribution. You may reproduce and distribute copies of the\r\n      Work or Derivative Works thereof in any medium, with or without\r\n      modifications, and in Source or Object form, provided that You\r\n      meet the following conditions:\r\n\r\n      (a) You must give any other recipients of the Work or\r\n          Derivative Works a copy of this License; and\r\n\r\n      (b) You must cause any modified files to carry prominent notices\r\n          stating that You changed the files; and\r\n\r\n      (c) You must retain, in the Source form of any Derivative Works\r\n          that You distribute, all copyright, patent, trademark, and\r\n          attribution notices from the Source form of the Work,\r\n          excluding those notices that do not pertain to any part of\r\n          the Derivative Works; and\r\n\r\n      (d) If the Work includes a \"NOTICE\" text file as part of its\r\n          distribution, then any Derivative Works that You distribute must\r\n          include a readable copy of the attribution notices contained\r\n          within such NOTICE file, excluding those notices that do not\r\n          pertain to any part of the Derivative Works, in at least one\r\n          of the following places: within a NOTICE text file distributed\r\n          as part of the Derivative Works; within the Source form or\r\n          documentation, if provided along with the Derivative Works; or,\r\n          within a display generated by the Derivative Works, if and\r\n          wherever such third-party notices normally appear. The contents\r\n          of the NOTICE file are for informational purposes only and\r\n          do not modify the License. You may add Your own attribution\r\n          notices within Derivative Works that You distribute, alongside\r\n          or as an addendum to the NOTICE text from the Work, provided\r\n          that such additional attribution notices cannot be construed\r\n          as modifying the License.\r\n\r\n      You may add Your own copyright statement to Your modifications and\r\n      may provide additional or different license terms and conditions\r\n      for use, reproduction, or distribution of Your modifications, or\r\n      for any such Derivative Works as a whole, provided Your use,\r\n      reproduction, and distribution of the Work otherwise complies with\r\n      the conditions stated in this License.\r\n\r\n   5. Submission of Contributions. Unless You explicitly state otherwise,\r\n      any Contribution intentionally submitted for inclusion in the Work\r\n      by You to the Licensor shall be under the terms and conditions of\r\n      this License, without any additional terms or conditions.\r\n      Notwithstanding the above, nothing herein shall supersede or modify\r\n      the terms of any separate license agreement you may have executed\r\n      with Licensor regarding such Contributions.\r\n\r\n   6. Trademarks. This License does not grant permission to use the trade\r\n      names, trademarks, service marks, or product names of the Licensor,\r\n      except as required for reasonable and customary use in describing the\r\n      origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n   7. Disclaimer of Warranty. Unless required by applicable law or\r\n      agreed to in writing, Licensor provides the Work (and each\r\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n      implied, including, without limitation, any warranties or conditions\r\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n      PARTICULAR PURPOSE. You are solely responsible for determining the\r\n      appropriateness of using or redistributing the Work and assume any\r\n      risks associated with Your exercise of permissions under this License.\r\n\r\n   8. Limitation of Liability. In no event and under no legal theory,\r\n      whether in tort (including negligence), contract, or otherwise,\r\n      unless required by applicable law (such as deliberate and grossly\r\n      negligent acts) or agreed to in writing, shall any Contributor be\r\n      liable to You for damages, including any direct, indirect, special,\r\n      incidental, or consequential damages of any character arising as a\r\n      result of this License or out of the use or inability to use the\r\n      Work (including but not limited to damages for loss of goodwill,\r\n      work stoppage, computer failure or malfunction, or any and all\r\n      other commercial damages or losses), even if such Contributor\r\n      has been advised of the possibility of such damages.\r\n\r\n   9. Accepting Warranty or Additional Liability. While redistributing\r\n      the Work or Derivative Works thereof, You may choose to offer,\r\n      and charge a fee for, acceptance of support, warranty, indemnity,\r\n      or other liability obligations and/or rights consistent with this\r\n      License. However, in accepting such obligations, You may act only\r\n      on Your own behalf and on Your sole responsibility, not on behalf\r\n      of any other Contributor, and only if You agree to indemnify,\r\n      defend, and hold each Contributor harmless for any liability\r\n      incurred by, or claims asserted against, such Contributor by reason\r\n      of your accepting any such warranty or additional liability.\r\n\r\n   END OF TERMS AND CONDITIONS\r\n\r\n   APPENDIX: How to apply the Apache License to your work.\r\n\r\n      To apply the Apache License to your work, attach the following\r\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\r\n      replaced with your own identifying information. (Don't include\r\n      the brackets!)  The text should be enclosed in the appropriate\r\n      comment syntax for the file format. We also recommend that a\r\n      file or class name and description of purpose be included on the\r\n      same \"printed page\" as the copyright notice for easier\r\n      identification within third-party archives.\r\n\r\n   Copyright 2023 The k8sgpt Authors\r\n\r\n   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n   you may not use this file except in compliance with the License.\r\n   You may obtain a copy of the License at\r\n\r\n       http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n   Unless required by applicable law or agreed to in writing, software\r\n   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n   See the License for the specific language governing permissions and\r\n   limitations under the License."
  },
  {
    "path": "MCP.md",
    "content": "# K8sGPT Model Context Protocol (MCP) Server\n\nK8sGPT provides a Model Context Protocol (MCP) server that exposes Kubernetes cluster operations as standardized tools, resources, and prompts for AI assistants like Claude, ChatGPT, and other MCP-compatible clients.\n\n## Table of Contents\n\n- [What is MCP?](#what-is-mcp)\n- [Quick Start](#quick-start)\n- [Server Modes](#server-modes)\n- [Available Tools](#available-tools)\n- [Available Resources](#available-resources)\n- [Available Prompts](#available-prompts)\n- [Usage Examples](#usage-examples)\n- [Integration with AI Assistants](#integration-with-ai-assistants)\n- [HTTP API Reference](#http-api-reference)\n\n## What is MCP?\n\nThe Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect to external data sources and tools. K8sGPT's MCP server exposes Kubernetes operations through this standardized interface, allowing AI assistants to:\n\n- Analyze cluster health and issues\n- Query Kubernetes resources\n- Access pod logs and events\n- Get troubleshooting guidance\n- Manage analyzer filters\n\n## Quick Start\n\n### Start the MCP Server\n\n**Stdio mode (for local AI assistants):**\n```bash\nk8sgpt serve --mcp\n```\n\n**HTTP mode (for network access):**\n```bash\nk8sgpt serve --mcp --mcp-http --mcp-port 8089\n```\n\n### Test with curl\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"tools/list\"\n  }'\n```\n\n## Server Modes\n\n### Stdio Mode (Default)\n\nUsed by local AI assistants like Claude Desktop:\n\n```bash\nk8sgpt serve --mcp\n```\n\nConfigure in your MCP client (e.g., Claude Desktop's `claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"k8sgpt\": {\n      \"command\": \"k8sgpt\",\n      \"args\": [\"serve\", \"--mcp\"]\n    }\n  }\n}\n```\n\n### HTTP Mode\n\nUsed for network access and webhooks:\n\n```bash\nk8sgpt serve --mcp --mcp-http --mcp-port 8089\n```\n\nThe server runs in stateless mode, so no session management is required. Each request is independent.\n\n## Available Tools\n\nThe MCP server exposes 12 tools for Kubernetes operations:\n\n### Cluster Analysis\n\n**analyze**\n- Analyze Kubernetes resources for issues and problems\n- Parameters:\n  - `namespace` (optional): Namespace to analyze\n  - `explain` (optional): Get AI explanations for issues\n  - `filters` (optional): Comma-separated list of analyzers to use\n\n**cluster-info**\n- Get Kubernetes cluster information and version\n\n### Resource Management\n\n**list-resources**\n- List Kubernetes resources of a specific type\n- Parameters:\n  - `resourceType` (required): Type of resource (pods, deployments, services, nodes, jobs, cronjobs, statefulsets, daemonsets, replicasets, configmaps, secrets, ingresses, pvcs, pvs)\n  - `namespace` (optional): Namespace to query\n  - `labelSelector` (optional): Label selector for filtering\n\n**get-resource**\n- Get detailed information about a specific Kubernetes resource\n- Parameters:\n  - `resourceType` (required): Type of resource\n  - `name` (required): Resource name\n  - `namespace` (optional): Namespace\n\n**list-namespaces**\n- List all namespaces in the cluster\n\n### Debugging and Troubleshooting\n\n**get-logs**\n- Get logs from a pod container\n- Parameters:\n  - `podName` (required): Name of the pod\n  - `namespace` (optional): Namespace\n  - `container` (optional): Container name\n  - `tail` (optional): Number of lines to show\n  - `previous` (optional): Show logs from previous container instance\n  - `sinceSeconds` (optional): Show logs from last N seconds\n\n**list-events**\n- List Kubernetes events for debugging\n- Parameters:\n  - `namespace` (optional): Namespace to query\n  - `involvedObjectName` (optional): Filter by object name\n  - `involvedObjectKind` (optional): Filter by object kind\n\n### Analyzer Management\n\n**list-filters**\n- List all available and active analyzers/filters\n\n**add-filters**\n- Add filters to enable specific analyzers\n- Parameters:\n  - `filters` (required): Comma-separated list of analyzer names\n\n**remove-filters**\n- Remove filters to disable specific analyzers\n- Parameters:\n  - `filters` (required): Comma-separated list of analyzer names\n\n### Integrations\n\n**list-integrations**\n- List available integrations (Prometheus, AWS, Keda, Kyverno, etc.)\n\n### Configuration\n\n**config**\n- Configure K8sGPT settings including custom analyzers and cache\n\n## Available Resources\n\nResources provide read-only access to cluster information:\n\n**cluster-info**\n- URI: `cluster-info`\n- Get information about the Kubernetes cluster\n\n**namespaces**\n- URI: `namespaces`\n- List all namespaces in the cluster\n\n**active-filters**\n- URI: `active-filters`\n- Get currently active analyzers/filters\n\n## Available Prompts\n\nPrompts provide guided troubleshooting workflows:\n\n**troubleshoot-pod**\n- Interactive pod debugging workflow\n- Arguments:\n  - `podName` (required): Name of the pod to troubleshoot\n  - `namespace` (required): Namespace of the pod\n\n**troubleshoot-deployment**\n- Interactive deployment debugging workflow\n- Arguments:\n  - `deploymentName` (required): Name of the deployment\n  - `namespace` (required): Namespace of the deployment\n\n**troubleshoot-cluster**\n- General cluster troubleshooting workflow\n\n## Usage Examples\n\n### Example 1: Analyze a Namespace\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 1,\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"analyze\",\n      \"arguments\": {\n        \"namespace\": \"production\",\n        \"explain\": \"true\"\n      }\n    }\n  }'\n```\n\n### Example 2: List Pods\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 2,\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"list-resources\",\n      \"arguments\": {\n        \"resourceType\": \"pods\",\n        \"namespace\": \"default\"\n      }\n    }\n  }'\n```\n\n### Example 3: Get Pod Logs\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 3,\n    \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"get-logs\",\n      \"arguments\": {\n        \"podName\": \"nginx-abc123\",\n        \"namespace\": \"default\",\n        \"tail\": \"100\"\n      }\n    }\n  }'\n```\n\n### Example 4: Access a Resource\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 4,\n    \"method\": \"resources/read\",\n    \"params\": {\n      \"uri\": \"namespaces\"\n    }\n  }'\n```\n\n### Example 5: Get a Troubleshooting Prompt\n\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\",\n    \"id\": 5,\n    \"method\": \"prompts/get\",\n    \"params\": {\n      \"name\": \"troubleshoot-pod\",\n      \"arguments\": {\n        \"podName\": \"nginx-abc123\",\n        \"namespace\": \"default\"\n      }\n    }\n  }'\n```\n\n## Integration with AI Assistants\n\n### Claude Desktop\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"k8sgpt\": {\n      \"command\": \"k8sgpt\",\n      \"args\": [\"serve\", \"--mcp\"]\n    }\n  }\n}\n```\n\nRestart Claude Desktop and you'll see k8sgpt tools available in the tool selector.\n\n### Custom MCP Clients\n\nAny MCP-compatible client can connect to the k8sgpt server. For HTTP-based clients:\n\n1. Start the server: `k8sgpt serve --mcp --mcp-http --mcp-port 8089`\n2. Connect to: `http://localhost:8089/mcp`\n3. Use standard MCP protocol methods: `tools/list`, `tools/call`, `resources/read`, `prompts/get`\n\n## HTTP API Reference\n\n### Endpoint\n\n```\nPOST http://localhost:8089/mcp\nContent-Type: application/json\n```\n\n### Request Format\n\nAll requests follow the JSON-RPC 2.0 format:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"method_name\",\n  \"params\": {\n    ...\n  }\n}\n```\n\n### Discovery Methods\n\n**List Tools**\n```json\n{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/list\"}\n```\n\n**List Resources**\n```json\n{\"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"resources/list\"}\n```\n\n**List Prompts**\n```json\n{\"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"prompts/list\"}\n```\n\n### Tool Invocation\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 4,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"tool_name\",\n    \"arguments\": {\n      \"arg1\": \"value1\",\n      \"arg2\": \"value2\"\n    }\n  }\n}\n```\n\n### Resource Access\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 5,\n  \"method\": \"resources/read\",\n  \"params\": {\n    \"uri\": \"resource_uri\"\n  }\n}\n```\n\n### Prompt Access\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 6,\n  \"method\": \"prompts/get\",\n  \"params\": {\n    \"name\": \"prompt_name\",\n    \"arguments\": {\n      \"arg1\": \"value1\"\n    }\n  }\n}\n```\n\n### Response Format\n\nSuccessful responses:\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"result\": {\n    ...\n  }\n}\n```\n\nError responses:\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"error\": {\n    \"code\": -32600,\n    \"message\": \"Error description\"\n  }\n}\n```\n\n## Advanced Configuration\n\n### Custom Port\n\n```bash\nk8sgpt serve --mcp --mcp-http --mcp-port 9000\n```\n\n### With Specific Backend\n\n```bash\nk8sgpt serve --mcp --backend openai\n```\n\n### With Kubeconfig\n\n```bash\nk8sgpt serve --mcp --kubeconfig ~/.kube/config\n```\n\n## Troubleshooting\n\n### Connection Issues\n\nVerify the server is running:\n```bash\ncurl http://localhost:8089/mcp\n```\n\n### Permission Issues\n\nEnsure your kubeconfig has appropriate cluster access:\n```bash\nkubectl cluster-info\n```\n\n### Tool Errors\n\nList available tools to verify names:\n```bash\ncurl -X POST http://localhost:8089/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/list\"}'\n```\n\n## Learn More\n\n- [MCP Specification](https://modelcontextprotocol.io/)\n- [K8sGPT Documentation](https://docs.k8sgpt.ai/)\n- [MCP Go Library](https://github.com/mark3labs/mcp-go)\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright 2023 K8sgpt AI. All rights reserved.\n# Use of this source code is governed by a MIT style\n# license that can be found in the LICENSE file.\n\n# ==============================================================================\n# define the default goal\n#\nROOT_PACKAGE=github.com/k8sgpt-ai/k8sgpt\n\nSHELL := /bin/bash\nDIRS=$(shell ls)\nGO=go\nGOOS ?= $(shell go env GOOS)\nGOARCH ?= $(shell go env GOARCH)\n\n.DEFAULT_GOAL := help\n\n# include the common makefile\nCOMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))\n# ROOT_DIR: root directory of the code base\nifeq ($(origin ROOT_DIR),undefined)\nROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/. && pwd -P))\nendif\n# OUTPUT_DIR: The directory where the build output is stored.\nifeq ($(origin OUTPUT_DIR),undefined)\nOUTPUT_DIR := $(ROOT_DIR)/bin\n$(shell mkdir -p $(OUTPUT_DIR))\nendif\n\nifeq ($(origin VERSION), undefined)\nVERSION := $(shell git describe --abbrev=0 --dirty --always --tags | sed 's/-/./g')\nendif\n\n# Check if the tree is dirty. default to dirty(maybe u should commit?)\nGIT_TREE_STATE:=\"dirty\"\nifeq (, $(shell git status --porcelain 2>/dev/null))\n\tGIT_TREE_STATE=\"clean\"\nendif\nGIT_COMMIT:=$(shell git rev-parse HEAD)\n\nIMG ?= ghcr.io/k8sgpt-ai/k8sgpt:latest\n\nBUILDFILE = \"./main.go\"\nBUILDAPP = \"$(OUTPUT_DIR)/k8sgpt\"\n\n.PHONY: all\nall: tidy add-copyright lint cover build\n\n# ==============================================================================\n# Targets\n\n## build: Build binaries by default\n.PHONY: build\nbuild: \n\t@echo \"$(shell go version)\"\n\t@echo \"===========> Building binary $(BUILDAPP) *[Git Info]: $(VERSION)-$(GIT_COMMIT)\"\n\t@export CGO_ENABLED=0 && go build -o $(BUILDAPP) -ldflags \"-s -w -X main.version=dev -X main.commit=$$(git rev-parse --short HEAD) -X main.date=$$(date +%FT%TZ)\" $(BUILDFILE)\n\n## tidy: tidy go.mod\n.PHONY: tidy\ntidy:\n\t@$(GO) mod tidy\n\n## deploy: Deploy k8sgpt\n.PHONY: deploy\ndeploy: helm\n\t@echo \"===========> Deploying k8sgpt\"\n\t$(HELM) install k8sgpt charts/k8sgpt -n k8sgpt --create-namespace\n\n## update: Update k8sgpt\n.PHONY: update\nupdate: helm\n\t@echo \"===========> Updating k8sgpt\"\n\t$(HELM) upgrade k8sgpt charts/k8sgpt -n k8sgpt\n\n## undeploy: Undeploy k8sgpt\n.PHONY: undeploy\nundeploy: helm\n\t@echo \"===========> Undeploying k8sgpt\"\n\t$(HELM) uninstall k8sgpt -n k8sgpt\n\n## docker-build: Build docker image\n.PHONY: docker-build\ndocker-build:\n\t@echo \"===========> Building docker image\"\n\tdocker buildx build --build-arg=VERSION=\"$$(git describe --tags --abbrev=0)\" --build-arg=COMMIT=\"$$(git rev-parse --short HEAD)\" --build-arg DATE=\"$$(date +%FT%TZ)\" --platform=\"linux/amd64,linux/arm64\" -t ${IMG} -f container/Dockerfile . --push\n\n## docker-build-local: Build docker image for local testing\n.PHONY: docker-build-local\ndocker-build-local:\n\t@echo \"===========> Building docker image for local testing\"\n\tdocker build --build-arg=VERSION=\"$$(git describe --tags --abbrev=0)\" --build-arg=COMMIT=\"$$(git rev-parse --short HEAD)\" --build-arg DATE=\"$$(date +%FT%TZ)\" -t k8sgpt:local -f container/Dockerfile .\n\n## fmt: Run go fmt against code.\n.PHONY: fmt\nfmt:\n\t@$(GO) fmt ./...\n\n## vet: Run go vet against code.\n.PHONY: vet\nvet:\n\t@$(GO) vet ./...\n\n## lint: Run go lint against code.\n.PHONY: lint\nlint:\n\t@golangci-lint run -v --timeout=5m ./...\n\n## style: Code style -> fmt,vet,lint\n.PHONY: style\nstyle: fmt vet lint\n\n## test: Run unit test\n.PHONY: test\ntest: \n\t@echo \"===========> Run unit test\"\n\t@$(GO) test ./... \n\n## cover: Run unit test with coverage\n.PHONY: cover\ncover: test\n\t@$(GO) test -cover\n\n## go.clean: Clean all builds\n.PHONY: clean\nclean:\n\t@echo \"===========> Cleaning all builds OUTPUT_DIR($(OUTPUT_DIR))\"\n\t@-rm -vrf $(OUTPUT_DIR)\n\t@echo \"===========> End clean...\"\n\n## help: Show this help info.\n.PHONY: help\nhelp: Makefile\n\t@printf \"\\n\\033[1mUsage: make <TARGETS> ...\\033[0m\\n\\n\\\\033[1mTargets:\\\\033[0m\\n\\n\"\n\t@sed -n 's/^##//p' $< | awk -F':' '{printf \"\\033[36m%-28s\\033[0m %s\\n\", $$1, $$2}' | sed -e 's/^/ /'\n\n## copyright.verify: Validate boilerplate headers for assign files\n.PHONY: copyright.verify\ncopyright.verify: tools.verify.addlicense\n\t@echo \"===========> Validate boilerplate headers for assign files starting in the $(ROOT_DIR) directory\"\n#\t@addlicense -v -check -ignore **/test/** -f $(LICENSE_TEMPLATE) $(CODE_DIRS)\n\t@echo \"===========> End of boilerplate headers check...\"\n\n## copyright.add: Add the boilerplate headers for all files\n.PHONY: copyright.add\ncopyright.add: tools.verify.addlicense\n\t@echo \"===========> Adding $(LICENSE_TEMPLATE) the boilerplate headers for all files\"\n#\t@addlicense -y $(shell date +\"%Y\") -v -c \"K8sgpt AI.\" -f $(LICENSE_TEMPLATE) $(CODE_DIRS)\n\t@echo \"===========> End the copyright is added...\"\n\n# =====\n# Tools\n\nHELM_VERSION ?= v3.11.3\n\nhelm:\n\tif ! test -f  $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); then \\\n\t\tcurl -L https://get.helm.sh/helm-$(HELM_VERSION)-$(GOOS)-$(GOARCH).tar.gz | tar xz; \\\n\t\tmv $(GOOS)-$(GOARCH)/helm $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); \\\n\t\tchmod +x $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); \\\n\t\trm -rf ./$(GOOS)-$(GOARCH)/; \\\n\tfi\nHELM=$(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH)"
  },
  {
    "path": "README.md",
    "content": "<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"./images/banner-white.png\" width=\"600px;\">\n  <img alt=\"Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'\" src=\"./images/banner-black.png\" width=\"600px;\">\n</picture>\n<br/>\n\n![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/k8sgpt-ai/k8sgpt)\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/k8sgpt-ai/k8sgpt/release.yaml)\n![GitHub release (latest by date)](https://img.shields.io/github/v/release/k8sgpt-ai/k8sgpt)\n[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7272/badge)](https://bestpractices.coreinfrastructure.org/projects/7272)\n[![Link to documentation](https://img.shields.io/static/v1?label=%F0%9F%93%96&message=Documentation&color=blue)](https://docs.k8sgpt.ai/)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt?ref=badge_shield)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Go version](https://img.shields.io/github/go-mod/go-version/k8sgpt-ai/k8sgpt.svg)](https://github.com/k8sgpt-ai/k8sgpt)\n[![codecov](https://codecov.io/github/k8sgpt-ai/k8sgpt/graph/badge.svg?token=ZLR7NG8URE)](https://codecov.io/github/k8sgpt-ai/k8sgpt)\n![GitHub last commit (branch)](https://img.shields.io/github/last-commit/k8sgpt-ai/k8sgpt/main)\n\n`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.\n\nIt has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.\n\n_Out of the box integration with OpenAI, Azure, Cohere, Amazon Bedrock, Google Gemini and local models._\n\n\n> **Sister project:** Check out [sympozium](https://github.com/AlexsJones/sympozium/) for managing agents in Kubernetes.\n\n\n<a href=\"https://www.producthunt.com/posts/k8sgpt?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-k8sgpt\" target=\"_blank\"><img src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=389489&theme=light\" alt=\"K8sGPT - K8sGPT&#0032;gives&#0032;Kubernetes&#0032;Superpowers&#0032;to&#0032;everyone | Product Hunt\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /></a> <a href=\"https://hellogithub.com/repository/9dfe44c18dfb4d6fa0181baf8b2cf2e1\" target=\"_blank\"><img src=\"https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=9dfe44c18dfb4d6fa0181baf8b2cf2e1&claim_uid=gqG4wmzkMrP0eFy\" alt=\"Featured｜HelloGitHub\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /></a>\n\n\n<img src=\"images/demo4.gif\" width=\"650px\">\n\n# Table of Contents\n- [Overview](#k8sgpt)\n- [Installation](#cli-installation)\n- [Quick Start](#quick-start)\n- [Analyzers](#analyzers)\n- [Examples](#examples)\n- [LLM AI Backends](#llm-ai-backends)\n- [Key Features](#key-features)\n- [Model Context Protocol (MCP)](#model-context-protocol-mcp)\n- [Documentation](#documentation)\n- [Contributing](#contributing)\n- [Community](#community)\n- [License](#license)\n\n# CLI Installation\n\n### Linux/Mac via brew\n\n```sh\nbrew install k8sgpt\n```\n\nor\n\n```sh\nbrew tap k8sgpt-ai/k8sgpt\nbrew install k8sgpt\n```\n\n<details>\n  <summary>RPM-based installation (RedHat/CentOS/Fedora)</summary>\n\n**32 bit:**\n\n  <!---x-release-please-start-version-->\n\n  ```\n  sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.rpm\n  ```\n  <!---x-release-please-end-->\n\n**64 bit:**\n\n  <!---x-release-please-start-version-->\n  ```\n  sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.rpm\n  ```\n  <!---x-release-please-end-->\n</details>\n\n<details>\n  <summary>DEB-based installation (Ubuntu/Debian)</summary>\n\n**32 bit:**\n\n  <!---x-release-please-start-version-->\n\n```\ncurl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.deb\nsudo dpkg -i k8sgpt_386.deb\n```\n\n  <!---x-release-please-end-->\n\n**64 bit:**\n\n  <!---x-release-please-start-version-->\n\n```\ncurl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.deb\nsudo dpkg -i k8sgpt_amd64.deb\n```\n\n  <!---x-release-please-end-->\n</details>\n\n<details>\n\n  <summary>APK-based installation (Alpine)</summary>\n\n**32 bit:**\n\n  <!---x-release-please-start-version-->\n  ```\n  wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.apk\n  apk add --allow-untrusted k8sgpt_386.apk\n  ```\n  <!---x-release-please-end-->\n\n**64 bit:**\n\n  <!---x-release-please-start-version-->\n  ```\n  wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.apk\n  apk add --allow-untrusted k8sgpt_amd64.apk\n  ```\n  <!---x-release-please-end-->\n</details>\n\n<details>\n  <summary>Failing Installation on WSL or Linux (missing gcc)</summary>\n  When installing Homebrew on WSL or Linux, you may encounter the following error:\n\n```\n==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be\nbuilt from the source. k8sgpt Install Clang or run brew install gcc.\n```\n\nIf you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.\n\n```\n   sudo apt-get update\n   sudo apt-get install build-essential\n```\n\n</details>\n\n### Windows\n\n- Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)\n  tab based on your system architecture.\n- Extract the downloaded package to your desired location. Configure the system _PATH_ environment variable with the binary location\n\n## Operator Installation\n\nTo install within a Kubernetes cluster please use our `k8sgpt-operator` with installation instructions available [here](https://github.com/k8sgpt-ai/k8sgpt-operator)\n\n_This mode of operation is ideal for continuous monitoring of your cluster and can integrate with your existing monitoring such as Prometheus and Alertmanager._\n\n## Quick Start\n\n- Currently, the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)\n  - You can do this by running `k8sgpt generate` to open a browser link to generate it\n- Run `k8sgpt auth add` to set it in k8sgpt.\n  - You can provide the password directly using the `--password` flag.\n- Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis.\n- Run `k8sgpt analyze` to run a scan.\n- And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.\n- You also run `k8sgpt analyze --with-doc` (with or without the explain flag) to get the official documentation from Kubernetes.\n\n# Using with Claude Desktop\n\nK8sGPT can be integrated with Claude Desktop to provide AI-powered Kubernetes cluster analysis. This integration requires K8sGPT v0.4.14 or later.\n\n## Prerequisites\n\n1. Install K8sGPT v0.4.14 or later:\n   ```sh\n   brew install k8sgpt\n   ```\n\n2. Install Claude Desktop from the official website\n\n3. Configure K8sGPT with your preferred AI backend:\n   ```sh\n   k8sgpt auth\n   ```\n\n## Setup\n\n1. Start the K8sGPT MCP server:\n   ```sh\n   k8sgpt serve --mcp\n   ```\n\n2. In Claude Desktop:\n   - Open Settings\n   - Navigate to the Integrations section\n   - Add K8sGPT as a new integration\n   - The MCP server will be automatically detected\n\n3. Configure Claude Desktop with the following JSON:\n\n  ```json\n  {\n    \"mcpServers\": {\n      \"k8sgpt\": {\n        \"command\": \"k8sgpt\",\n        \"args\": [\n          \"serve\",\n          \"--mcp\"\n        ]\n      }\n    }\n  }\n  ```\n\n## Usage\n\nOnce connected, you can use Claude Desktop to:\n- Analyze your Kubernetes cluster\n- Get detailed insights about cluster health\n- Receive recommendations for fixing issues\n- Query cluster information\n\nExample commands in Claude Desktop:\n- \"Analyze my Kubernetes cluster\"\n- \"What's the health status of my cluster?\"\n- \"Show me any issues in the default namespace\"\n\n## Troubleshooting\n\nIf you encounter connection issues:\n1. Ensure K8sGPT is running with the MCP server enabled\n2. Verify your Kubernetes cluster is accessible\n3. Check that your AI backend is properly configured\n4. Restart both K8sGPT and Claude Desktop\n\nFor more information, visit our [documentation](https://docs.k8sgpt.ai).\n\n## Analyzers\n\nK8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but\nyou will be able to write your own analyzers.\n\n### Built in analyzers\n\n#### Enabled by default\n\n- [x] podAnalyzer\n- [x] pvcAnalyzer\n- [x] rsAnalyzer\n- [x] serviceAnalyzer\n- [x] eventAnalyzer\n- [x] ingressAnalyzer\n- [x] statefulSetAnalyzer\n- [x] deploymentAnalyzer\n- [x] jobAnalyzer\n- [x] cronJobAnalyzer\n- [x] nodeAnalyzer\n- [x] mutatingWebhookAnalyzer\n- [x] validatingWebhookAnalyzer\n- [x] configMapAnalyzer\n\n#### Optional\n\n- [x] hpaAnalyzer\n- [x] pdbAnalyzer\n- [x] networkPolicyAnalyzer\n- [x] gatewayClass\n- [x] gateway\n- [x] httproute\n- [x] logAnalyzer\n- [x] storageAnalyzer\n- [x] securityAnalyzer\n- [x] CatalogSource\n- [x] ClusterCatalog\n- [x] ClusterExtension\n- [x] ClusterService\n- [x] ClusterServiceVersion\n- [x] OperatorGroup\n- [x] InstallPlan\n- [x] Subscription\n\n## Examples\n\n_Run a scan with the default analyzers_\n\n```\nk8sgpt generate\nk8sgpt auth add\nk8sgpt analyze --explain\nk8sgpt analyze --explain --with-doc\n```\n\n_Filter on resource_\n\n```\nk8sgpt analyze --explain --filter=Service\n```\n\n_Filter by namespace_\n\n```\nk8sgpt analyze --explain --filter=Pod --namespace=default\n```\n\n_Output to JSON_\n\n```\nk8sgpt analyze --explain --filter=Service --output=json\n```\n\n_Anonymize during explain_\n\n```\nk8sgpt analyze --explain --filter=Service --output=json --anonymize\n```\n\n<details>\n<summary> Using filters </summary>\n\n_List filters_\n\n```\nk8sgpt filters list\n```\n\n_Add default filters_\n\n```\nk8sgpt filters add [filter(s)]\n```\n\n### Examples :\n\n- Simple filter : `k8sgpt filters add Service`\n- Multiple filters : `k8sgpt filters add Ingress,Pod`\n\n_Remove default filters_\n\n```\nk8sgpt filters remove [filter(s)]\n```\n\n### Examples :\n\n- Simple filter : `k8sgpt filters remove Service`\n- Multiple filters : `k8sgpt filters remove Ingress,Pod`\n\n</details>\n\n<details>\n\n<summary> Additional commands </summary>\n\n_List configured backends_\n\n```\nk8sgpt auth list\n```\n\n_Update configured backends_\n\n```\nk8sgpt auth update $MY_BACKEND1,$MY_BACKEND2..\n```\n\n_Remove configured backends_\n\n```\nk8sgpt auth remove -b $MY_BACKEND1,$MY_BACKEND2..\n```\n\n_List integrations_\n\n```\nk8sgpt integrations list\n```\n\n_Activate integrations_\n\n```\nk8sgpt integrations activate [integration(s)]\n```\n\n_Use integration_\n\n```\nk8sgpt analyze --filter=[integration(s)]\n```\n\n_Deactivate integrations_\n\n```\nk8sgpt integrations deactivate [integration(s)]\n```\n\n_Serve mode_\n\n```\nk8sgpt serve\n```\n\n_Serve mode with MCP (Model Context Protocol)_\n\n```\n# Enable MCP server on default port 8089\nk8sgpt serve --mcp --mcp-http\n\n# Enable MCP server on custom port\nk8sgpt serve --mcp --mcp-http --mcp-port 8089\n\n# Full serve mode with MCP\nk8sgpt serve --mcp --mcp-http --port 8080 --metrics-port 8081 --mcp-port 8089\n```\n\nThe MCP server enables integration with tools like Claude Desktop and other MCP-compatible clients. It runs on port 8089 by default and provides:\n- Kubernetes cluster analysis via MCP protocol\n- Resource information and health status\n- AI-powered issue explanations and recommendations\n\nFor Helm chart deployment with MCP support, see the `charts/k8sgpt/values-mcp-example.yaml` file.\n\n_Analysis with serve mode_\n\n```\ngrpcurl -plaintext -d '{\"namespace\": \"k8sgpt\", \"explain\" : \"true\"}' localhost:8080 schema.v1.ServerAnalyzerService/Analyze\n{\n  \"status\": \"OK\"\n}\n```\n\n_Analysis with custom headers_\n\n```\nk8sgpt analyze --explain --custom-headers CustomHeaderKey:CustomHeaderValue\n```\n\n_Print analysis stats_\n\n```\nk8sgpt analyze -s\nThe stats mode allows for debugging and understanding the time taken by an analysis by displaying the statistics of each analyzer.\n- Analyzer Ingress took 47.125583ms\n- Analyzer PersistentVolumeClaim took 53.009167ms\n- Analyzer CronJob took 57.517792ms\n- Analyzer Deployment took 156.6205ms\n- Analyzer Node took 160.109833ms\n- Analyzer ReplicaSet took 245.938333ms\n- Analyzer StatefulSet took 448.0455ms\n- Analyzer Pod took 5.662594708s\n- Analyzer Service took 38.583359166s\n```\n\n_Diagnostic information_\n\nTo collect diagnostic information use the following command to create a `dump_<timestamp>_json` in your local directory.\n```\nk8sgpt dump\n```\n\n</details>\n\n## LLM AI Backends\n\nK8sGPT uses the chosen LLM, generative AI provider when you want to explain the analysis results using --explain flag e.g. `k8sgpt analyze --explain`. You can use `--backend` flag to specify a configured provider (it's `openai` by default).\n\nYou can list available providers using `k8sgpt auth list`:\n\n```\nDefault:\n> openai\nActive:\nUnused:\n> openai\n> localai\n> ollama\n> azureopenai\n> cohere\n> amazonbedrock\n> amazonsagemaker\n> google\n> huggingface\n> noopai\n> googlevertexai\n> watsonxai\n> customrest\n> ibmwatsonxai\n```\n\nFor detailed documentation on how to configure and use each provider see [here](https://docs.k8sgpt.ai/reference/providers/backend/).\n\n_To set a new default provider_\n\n```\nk8sgpt auth default -p azureopenai\nDefault provider set to azureopenai\n```\n\n_Using Amazon Bedrock with inference profiles_\n\n_System Inference Profile_\n\n```\nk8sgpt auth add --backend amazonbedrock --providerRegion us-east-1 --model arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-inference-profile\n\n```\n\n_Application Inference Profile_\n\n```\nk8sgpt auth add --backend amazonbedrock --providerRegion us-east-1 --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/2uzp4s0w39t6\n\n```\n\n## Key Features\n\n<details>\n\nWith this option, the data is anonymized before being sent to the AI Backend. During the analysis execution, `k8sgpt` retrieves sensitive data (Kubernetes object names, labels, etc.). This data is masked when sent to the AI backend and replaced by a key that can be used to de-anonymize the data when the solution is returned to the user.\n\n<summary> Anonymization </summary>\n\n1. Error reported during analysis:\n\n```bash\nError: HorizontalPodAutoscaler uses StatefulSet/fake-deployment as ScaleTargetRef which does not exist.\n```\n\n2. Payload sent to the AI backend:\n\n```bash\nError: HorizontalPodAutoscaler uses StatefulSet/tGLcCRcHa1Ce5Rs as ScaleTargetRef which does not exist.\n```\n\n3. Payload returned by the AI:\n\n```bash\nThe Kubernetes system is trying to scale a StatefulSet named tGLcCRcHa1Ce5Rs using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.\n```\n\n4. Payload returned to the user:\n\n```bash\nThe Kubernetes system is trying to scale a StatefulSet named fake-deployment using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.\n```\n\n### Further Details\n\nNote: **Anonymization does not currently apply to events.**\n\n_In a few analysers like Pod, we feed to the AI backend the event messages which are not known beforehand thus we are not masking them for the **time being**._\n\n- The following is the list of analysers in which data is **being masked**:-\n\n  - Statefulset\n  - Service\n  - PodDisruptionBudget\n  - Node\n  - NetworkPolicy\n  - Ingress\n  - HPA\n  - Deployment\n  - Cronjob\n\n- The following is the list of analysers in which data is **not being masked**:-\n\n  - ReplicaSet\n  - PersistentVolumeClaim\n  - Pod\n  - Log\n  - **_\\*Events_**\n\n**\\*Note**:\n\n- k8gpt will not mask the above analysers because they do not send any identifying information except **Events** analyser.\n- Masking for **Events** analyzer is scheduled in the near future as seen in this [issue](https://github.com/k8sgpt-ai/k8sgpt/issues/560). _Further research has to be made to understand the patterns and be able to mask the sensitive parts of an event like pod name, namespace etc._\n\n- The following is the list of fields which are not **being masked**:-\n\n  - Describe\n  - ObjectStatus\n  - Replicas\n  - ContainerStatus\n  - **_\\*Event Message_**\n  - ReplicaStatus\n  - Count (Pod)\n\n**\\*Note**:\n\n- It is quite possible the payload of the event message might have something like \"super-secret-project-pod-X crashed\" which we don't currently redact _(scheduled in the near future as seen in this [issue](https://github.com/k8sgpt-ai/k8sgpt/issues/560))_.\n\n### Proceed with care\n\n- The K8gpt team recommends using an entirely different backend **(a local model) in critical production environments**. By using a local model, you can rest assured that everything stays within your DMZ, and nothing is leaked.\n- If there is any uncertainty about the possibility of sending data to a public LLM (open AI, Azure AI) and it poses a risk to business-critical operations, then, in such cases, the use of public LLM should be avoided based on personal assessment and the jurisdiction of risks involved.\n\n</details>\n\n<details>\n<summary> Configuration management</summary>\n\n`k8sgpt` stores config data in the `$XDG_CONFIG_HOME/k8sgpt/k8sgpt.yaml` file. The data is stored in plain text, including your OpenAI key.\n\nConfig file locations:\n| OS | Path |\n| ------- | ------------------------------------------------ |\n| MacOS | ~/Library/Application Support/k8sgpt/k8sgpt.yaml |\n| Linux | ~/.config/k8sgpt/k8sgpt.yaml |\n| Windows | %LOCALAPPDATA%/k8sgpt/k8sgpt.yaml |\n\n</details>\n\n<details>\nThere may be scenarios where caching remotely is preferred.\nIn these scenarios K8sGPT supports AWS S3 or Azure Blob storage Integration.\n\n<summary> Remote caching </summary>\n<em>Note: You can configure and use only one remote cache at a time</em>\n\n_Adding a remote cache_\n\n- AWS S3\n  - _As a prerequisite `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are required as environmental variables._\n  - Configuration, `k8sgpt cache add s3 --region <aws region> --bucket <name>`\n  - Minio Configuration with HTTP endpoint ` k8sgpt cache add s3 --bucket <name> --endpoint <http://localhost:9000>`\n  - Minio Configuration with HTTPs endpoint, skipping TLS verification ` k8sgpt cache add s3 --bucket <name> --endpoint <https://localhost:9000> --insecure`\n    - K8sGPT will create the bucket if it does not exist\n- Azure Storage\n  - We support a number of [techniques](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#2-authenticate-with-azure) to authenticate against Azure\n  - Configuration, `k8sgpt cache add azure --storageacc <storage account name> --container <container name>`\n    - K8sGPT assumes that the storage account already exist and it will create the container if it does not exist\n    - It is the **user** responsibility have to grant specific permissions to their identity in order to be able to upload blob files and create SA containers (e.g Storage Blob Data Contributor)\n- Google Cloud Storage\n  - _As a prerequisite `GOOGLE_APPLICATION_CREDENTIALS` are required as environmental variables._\n  - Configuration, ` k8sgpt cache add gcs --region <gcp region> --bucket <name> --projectid <project id>`\n    - K8sGPT will create the bucket if it does not exist\n\n_Listing cache items_\n\n```\nk8sgpt cache list\n```\n\n_Purging an object from the cache_\nNote: purging an object using this command will delete upstream files, so it requires appropriate permissions.\n\n```\nk8sgpt cache purge $OBJECT_NAME\n```\n\n_Removing the remote cache_\nNote: this will not delete the upstream S3 bucket or Azure storage container\n\n```\nk8sgpt cache remove\n```\n\n</details>\n\n<details>\n<summary> Custom Analyzers</summary>\n\nThere may be scenarios where you wish to write your own analyzer in a language of your choice.\nK8sGPT now supports the ability to do so by abiding by the [schema](https://github.com/k8sgpt-ai/schemas/blob/main/protobuf/schema/v1/custom_analyzer.proto) and serving the analyzer for consumption.\nTo do so, define the analyzer within the K8sGPT configuration and it will add it into the scanning process.\nIn addition to this you will need to enable the following flag on analysis:\n\n```\nk8sgpt analyze --custom-analysis\n```\n\nHere is an example local host analyzer in [Rust](https://github.com/k8sgpt-ai/host-analyzer)\nWhen this is run on `localhost:8080` the K8sGPT config can pick it up with the following additions:\n\n```\ncustom_analyzers:\n  - name: host-analyzer\n    connection:\n      url: localhost\n      port: 8080\n```\n\nThis now gives the ability to pass through hostOS information ( from this analyzer example ) to K8sGPT to use as context with normal analysis.\n\n_See the docs on how to write a custom analyzer_\n\n_Listing custom analyzers configured_\n```\nk8sgpt custom-analyzer list\n```\n\n_Adding custom analyzer without install_\n```\nk8sgpt custom-analyzer add --name my-custom-analyzer --port 8085\n```\n\n_Removing custom analyzer_\n```\nk8sgpt custom-analyzer remove --names \"my-custom-analyzer,my-custom-analyzer-2\"\n```\n\n</details>\n## Model Context Protocol (MCP)\n\nK8sGPT provides a Model Context Protocol server that exposes Kubernetes operations as standardized tools for AI assistants like Claude, ChatGPT, and other MCP-compatible clients.\n\n**Start the MCP server:**\n\nStdio mode (for local AI assistants):\n```bash\nk8sgpt serve --mcp\n```\n\nHTTP mode (for network access):\n```bash\nk8sgpt serve --mcp --mcp-http --mcp-port 8089\n```\n\n**Features:**\n- 12 tools for cluster analysis, resource management, and debugging\n- 3 resources for cluster information access\n- 3 interactive troubleshooting prompts\n- Stateless HTTP mode for one-off invocations\n- Full integration with Claude Desktop and other MCP clients\n\n**Learn more:** See [MCP.md](MCP.md) for complete documentation, usage examples, and integration guides.\n## Documentation\n\nFind our official documentation available [here](https://docs.k8sgpt.ai)\n\n## Contributing\n\nPlease read our [contributing guide](./CONTRIBUTING.md).\n\n## Community\n\nFind us on [Slack](https://join.slack.com/t/k8sgpt/shared_invite/zt-332vhyaxv-bfjJwHZLXWVCB3QaXafEYQ)\n\n<a href=\"https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt\" />\n</a>\n\n## License\n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt?ref=badge_large)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nWe currently support the latest release for security patching and will deploy forward releases.\n\nFor example if there is a vulnerability in release `0.1.0` we will fix that release in version `0.1.1-fix` or `0.1.1`\n\n## Reporting a Vulnerability\n\nIf you are aware of a vulnerability please feel free to disclose it responsibly to contact@k8sgpt.ai or to one of our maintainers in our Slack community.\n"
  },
  {
    "path": "SUPPORTED_MODELS.md",
    "content": "# Supported AI Providers and Models in K8sGPT\n\nK8sGPT supports a variety of AI/LLM providers (backends). Some providers have a fixed set of supported models, while others allow you to specify any model supported by the provider.\n\n---\n\n## Providers and Supported Models\n\n### OpenAI\n- **Model:** User-configurable (any model supported by OpenAI, e.g., `gpt-3.5-turbo`, `gpt-4`, etc.)\n\n### Azure OpenAI\n- **Model:** User-configurable (any model deployed in your Azure OpenAI resource)\n\n### LocalAI\n- **Model:** User-configurable (default: `llama3`)\n\n### Ollama\n- **Model:** User-configurable (default: `llama3`, others can be specified)\n\n### NoOpAI\n- **Model:** N/A (no real model, used for testing)\n\n### Cohere\n- **Model:** User-configurable (any model supported by Cohere)\n\n### Amazon Bedrock\n- **Supported Models:**\n  - anthropic.claude-sonnet-4-20250514-v1:0\n  - us.anthropic.claude-sonnet-4-20250514-v1:0\n  - eu.anthropic.claude-sonnet-4-20250514-v1:0\n  - apac.anthropic.claude-sonnet-4-20250514-v1:0\n  - us.anthropic.claude-3-7-sonnet-20250219-v1:0\n  - eu.anthropic.claude-3-7-sonnet-20250219-v1:0\n  - apac.anthropic.claude-3-7-sonnet-20250219-v1:0\n  - anthropic.claude-3-5-sonnet-20240620-v1:0\n  - us.anthropic.claude-3-5-sonnet-20241022-v2:0\n  - anthropic.claude-v2\n  - anthropic.claude-v1\n  - anthropic.claude-instant-v1\n  - ai21.j2-ultra-v1\n  - ai21.j2-jumbo-instruct\n  - amazon.titan-text-express-v1\n  - amazon.nova-pro-v1:0\n  - eu.amazon.nova-pro-v1:0\n  - us.amazon.nova-pro-v1:0\n  - amazon.nova-lite-v1:0\n  - eu.amazon.nova-lite-v1:0\n  - us.amazon.nova-lite-v1:0\n  - anthropic.claude-3-haiku-20240307-v1:0\n\n> **Note:**\n> If you use an AWS Bedrock inference profile ARN (e.g., `arn:aws:bedrock:us-east-1:<account>:application-inference-profile/<id>`) as the model, you must still provide a valid modelId (e.g., `anthropic.claude-3-sonnet-20240229-v1:0`). K8sGPT will automatically set the required `X-Amzn-Bedrock-Inference-Profile-ARN` header for you when making requests to Bedrock.\n\n### Amazon SageMaker\n- **Model:** User-configurable (any model deployed in your SageMaker endpoint)\n\n### Google GenAI\n- **Model:** User-configurable (any model supported by Google GenAI, e.g., `gemini-pro`)\n\n### Huggingface\n- **Model:** User-configurable (any model supported by Huggingface Inference API)\n\n### Google VertexAI\n- **Supported Models:**\n  - gemini-1.0-pro-001\n\n### OCI GenAI\n- **Model:** User-configurable (any model supported by OCI GenAI)\n\n### Custom REST\n- **Model:** User-configurable (any model your custom REST endpoint supports)\n\n### IBM Watsonx\n- **Supported Models:**\n  - ibm/granite-13b-chat-v2\n\n### Groq\n- **Model:** User-configurable (any model supported by Groq, e.g., `llama-3.3-70b-versatile`, `mixtral-8x7b-32768`)\n\n---\n\nFor more details on configuring each provider and model, refer to the official K8sGPT documentation and the provider's own documentation. "
  },
  {
    "path": "charts/k8sgpt/Chart.yaml",
    "content": "apiVersion: v2\nappVersion: v0.4.23 #x-release-please-version\ndescription: A Helm chart for K8SGPT\nname: k8sgpt\ntype: application\nversion: 1.0.0\n"
  },
  {
    "path": "charts/k8sgpt/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"k8sgpt.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"k8sgpt.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"k8sgpt.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"k8sgpt.labels\" -}}\nhelm.sh/chart: {{ include \"k8sgpt.chart\" . }}\napp.kubernetes.io/name: {{ include \"k8sgpt.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion }}\n{{- end }}\n{{- end }}"
  },
  {
    "path": "charts/k8sgpt/templates/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  {{- if .Values.deployment.annotations }}\n  annotations:\n  {{- toYaml .Values.deployment.annotations | nindent 4 }}\n  {{- end }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"k8sgpt.name\" . }}\n      app.kubernetes.io/instance: {{ .Release.Name }}\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: {{ include \"k8sgpt.name\" . }}\n        app.kubernetes.io/instance: {{ .Release.Name }}\n    spec:\n      {{- if .Values.deployment.securityContext }}\n      securityContext:\n        {{- toYaml .Values.deployment.securityContext | nindent 8 }}\n      {{- end }}\n      serviceAccountName: {{ template \"k8sgpt.fullname\" . }}\n      containers:\n      - name: k8sgpt-container\n        imagePullPolicy: {{ .Values.deployment.imagePullPolicy }}\n        image: {{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion }}\n        ports:\n        - containerPort: 8080\n        {{- if .Values.deployment.mcp.enabled }}\n        - containerPort: {{ .Values.deployment.mcp.port | int }}\n        {{- end }}\n        args: [\"serve\"\n        {{- if .Values.deployment.mcp.enabled }}, \"--mcp\", \"-v\",\"--mcp-http\", \"--mcp-port\", {{ .Values.deployment.mcp.port | quote }}\n        {{- end }}\n        ]\n        {{- if .Values.deployment.resources }}\n        resources:\n        {{- toYaml .Values.deployment.resources | nindent 10 }}\n        {{- end }}\n        env:\n        - name: K8SGPT_MODEL\n          value: {{ .Values.deployment.env.model }}\n        - name: K8SGPT_BACKEND\n          value: {{ .Values.deployment.env.backend }}\n        {{- if .Values.secret.secretKey }}\n        - name: K8SGPT_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              name: ai-backend-secret\n              key: secret-key\n        {{- end }}\n        - name: XDG_CONFIG_HOME\n          value: /k8sgpt-config/\n        - name: XDG_CACHE_HOME\n          value: /k8sgpt-config/\n        volumeMounts:\n        - mountPath: /k8sgpt-config\n          name: config\n      volumes:\n      - emptyDir: {}\n        name: config\n"
  },
  {
    "path": "charts/k8sgpt/templates/role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}\nrules:\n- apiGroups:\n  - '*'\n  resources:\n  - '*'\n  verbs:\n  - get\n  - list\n  - watch"
  },
  {
    "path": "charts/k8sgpt/templates/rolebinding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}\nsubjects:\n- kind: ServiceAccount\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\nroleRef:\n  kind: ClusterRole\n  name: {{ template \"k8sgpt.fullname\" . }}\n  apiGroup: rbac.authorization.k8s.io"
  },
  {
    "path": "charts/k8sgpt/templates/sa.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}"
  },
  {
    "path": "charts/k8sgpt/templates/secret.yaml",
    "content": "{{- if .Values.secret.secretKey }}\napiVersion: v1\ndata:\n  secret-key: {{ .Values.secret.secretKey }}\nkind: Secret\nmetadata:\n  name: ai-backend-secret\n  namespace: {{ .Release.Namespace | quote }}\ntype: Opaque\n{{- end}}"
  },
  {
    "path": "charts/k8sgpt/templates/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}\n  {{- if .Values.service.annotations }}\n  annotations:\n  {{- toYaml .Values.service.annotations | nindent 4 }}\n  {{- end }}\nspec:\n  selector:\n    app.kubernetes.io/name: {{ include \"k8sgpt.name\" . }}\n  ports:\n    - name: http\n      port: 8080\n      targetPort: 8080\n    - name: metrics\n      port: 8081\n      targetPort: 8081\n    {{- if .Values.deployment.mcp.enabled }}\n    - name: mcp\n      port: {{ .Values.deployment.mcp.port | int }}\n      targetPort: {{ .Values.deployment.mcp.port | int }}\n    {{- end }}\n  type: {{ .Values.service.type }}\n"
  },
  {
    "path": "charts/k8sgpt/templates/serviceMonitor.yaml",
    "content": "{{- if .Values.serviceMonitor.enabled }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  name: {{ template \"k8sgpt.fullname\" . }}\n  namespace: {{ .Release.Namespace | quote }}\n  labels:\n    {{- include \"k8sgpt.labels\" . | nindent 4 }}\n  {{- if .Values.serviceMonitor.additionalLabels }}\n    {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }}\n  {{- end }}\nspec:\n  endpoints:\n  - honorLabels: true\n    path: /metrics\n    port: metrics\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: {{ include \"k8sgpt.name\" . }}\n      app.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}"
  },
  {
    "path": "charts/k8sgpt/values-mcp-example.yaml",
    "content": "# Example values file to enable MCP (Model Context Protocol) service\n# Copy this file and modify as needed, then use: helm install -f values-mcp-example.yaml\n\ndeployment:\n  # Enable MCP server\n  mcp:\n    enabled: true\n    port: \"8089\"  # Port for MCP server (default: 8089)\n    http: true    # Enable HTTP mode for MCP server\n  \n  # Other deployment settings remain the same\n  image:\n    repository: ghcr.io/k8sgpt-ai/k8sgpt\n    tag: \"\" # defaults to Chart.appVersion if unspecified\n  imagePullPolicy: Always\n  env:\n    model: \"gpt-3.5-turbo\"\n    backend: \"openai\"\n  resources:\n    limits:\n      cpu: \"1\"\n      memory: \"512Mi\"\n    requests:\n      cpu: \"0.2\"\n      memory: \"156Mi\"\n\n# Service configuration\nservice:\n  type: ClusterIP\n  annotations: {}\n\n# Secret configuration for AI backend\nsecret:\n  secretKey: \"\" # base64 encoded OpenAI token\n\n# ServiceMonitor for Prometheus metrics\nserviceMonitor:\n  enabled: false\n  additionalLabels: {}\n"
  },
  {
    "path": "charts/k8sgpt/values.yaml",
    "content": "deployment:\n  image:\n    repository: ghcr.io/k8sgpt-ai/k8sgpt\n    tag: \"\" # defaults to Chart.appVersion if unspecified\n  imagePullPolicy: Always\n  annotations: {}\n  env:\n    model: \"gpt-3.5-turbo\"\n    backend: \"openai\" # one of: [ openai | llama ]\n  # MCP (Model Context Protocol) server configuration\n  mcp:\n    enabled: false # Enable MCP server\n    port: \"8089\" # Port for MCP server\n    http: true # Enable HTTP mode for MCP server\n  resources:\n    limits:\n      cpu: \"1\"\n      memory: \"512Mi\"\n    requests:\n      cpu: \"0.2\"\n      memory: \"156Mi\"\n  securityContext: {}\n  # Set securityContext.runAsUser/runAsGroup if necessary. Values below were taken from https://github.com/k8sgpt-ai/k8sgpt/blob/main/container/Dockerfile\n  # runAsUser: 65532\n  # runAsGroup: 65532\nsecret:\n  secretKey: \"\" # base64 encoded OpenAI token\n\nservice:\n  type: ClusterIP\n  annotations: {}\n\nserviceMonitor:\n  enabled: false\n  additionalLabels: {}\n"
  },
  {
    "path": "cmd/analyze/analyze.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyze\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai/interactive\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analysis\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\texplain         bool\n\tbackend         string\n\toutput          string\n\tfilters         []string\n\tlanguage        string\n\tnocache         bool\n\tnamespace       string\n\tlabelSelector   string\n\tanonymize       bool\n\tmaxConcurrency  int\n\twithDoc         bool\n\tinteractiveMode bool\n\tcustomAnalysis  bool\n\tcustomHeaders   []string\n\twithStats       bool\n)\n\n// AnalyzeCmd represents the problems command\nvar AnalyzeCmd = &cobra.Command{\n\tUse:     \"analyze\",\n\tAliases: []string{\"analyse\"},\n\tShort:   \"This command will find problems within your Kubernetes cluster\",\n\tLong: `This command will find problems within your Kubernetes cluster and\n\tprovide you with a list of issues that need to be resolved`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\t// Create analysis configuration first.\n\t\tconfig, err := analysis.NewAnalysis(\n\t\t\tbackend,\n\t\t\tlanguage,\n\t\t\tfilters,\n\t\t\tnamespace,\n\t\t\tlabelSelector,\n\t\t\tnocache,\n\t\t\texplain,\n\t\t\tmaxConcurrency,\n\t\t\twithDoc,\n\t\t\tinteractiveMode,\n\t\t\tcustomHeaders,\n\t\t\twithStats,\n\t\t)\n\n\t\tverbose := viper.GetBool(\"verbose\")\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Checking analysis configuration.\")\n\t\t}\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Analysis initialized.\")\n\t\t}\n\t\tdefer config.Close()\n\n\t\tif customAnalysis {\n\t\t\tconfig.RunCustomAnalysis()\n\t\t\tif verbose {\n\t\t\t\tfmt.Println(\"Debug: All custom analyzers completed.\")\n\t\t\t}\n\t\t}\n\t\tconfig.RunAnalysis()\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: All core analyzers completed.\")\n\t\t}\n\n\t\tif explain {\n\t\t\terr := config.GetAIResults(output, anonymize)\n\t\t\tif verbose {\n\t\t\t\tfmt.Println(\"Debug: Checking AI results.\")\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\t\t// print results\n\t\toutput_data, err := config.PrintOutput(output)\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Checking output.\")\n\t\t}\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tif withStats {\n\t\t\tstatsData := config.PrintStats()\n\t\t\tfmt.Println(string(statsData))\n\t\t}\n\n\t\tfmt.Println(string(output_data))\n\n\t\tif interactiveMode && explain {\n\t\t\tif output == \"json\" {\n\t\t\t\tcolor.Yellow(\"Caution: interactive mode using --json enabled may use additional tokens.\")\n\t\t\t}\n\t\t\tsigs := make(chan os.Signal, 1)\n\t\t\tsignal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)\n\t\t\tinteractiveClient := interactive.NewInteractionRunner(config, output_data)\n\n\t\t\tgo interactiveClient.StartInteraction()\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase res := <-sigs:\n\t\t\t\t\tswitch res {\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tos.Exit(0)\n\t\t\t\t\t}\n\t\t\t\tcase res := <-interactiveClient.State:\n\t\t\t\t\tswitch res {\n\t\t\t\t\tcase interactive.E_EXITED:\n\t\t\t\t\t\tos.Exit(0)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n}\n\nfunc init() {\n\t// namespace flag\n\tAnalyzeCmd.Flags().StringVarP(&namespace, \"namespace\", \"n\", \"\", \"Namespace to analyze\")\n\t// no cache flag\n\tAnalyzeCmd.Flags().BoolVarP(&nocache, \"no-cache\", \"c\", false, \"Do not use cached data\")\n\t// anonymize flag\n\tAnalyzeCmd.Flags().BoolVarP(&anonymize, \"anonymize\", \"a\", false, \"Anonymize data before sending it to the AI backend. This flag masks sensitive data, such as Kubernetes object names and labels, by replacing it with a key. However, please note that this flag does not currently apply to events.\")\n\t// array of strings flag\n\tAnalyzeCmd.Flags().StringSliceVarP(&filters, \"filter\", \"f\", []string{}, \"Filter for these analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)\")\n\t// explain flag\n\tAnalyzeCmd.Flags().BoolVarP(&explain, \"explain\", \"e\", false, \"Explain the problem to me\")\n\t// add flag for backend\n\tAnalyzeCmd.Flags().StringVarP(&backend, \"backend\", \"b\", \"\", \"Backend AI provider\")\n\t// output as json\n\tAnalyzeCmd.Flags().StringVarP(&output, \"output\", \"o\", \"text\", \"Output format (text, json)\")\n\t// add language options for output\n\tAnalyzeCmd.Flags().StringVarP(&language, \"language\", \"l\", \"english\", \"Languages to use for AI (e.g. 'English', 'Spanish', 'French', 'German', 'Italian', 'Portuguese', 'Dutch', 'Russian', 'Chinese', 'Japanese', 'Korean')\")\n\t// add max concurrency\n\tAnalyzeCmd.Flags().IntVarP(&maxConcurrency, \"max-concurrency\", \"m\", 10, \"Maximum number of concurrent requests to the Kubernetes API server\")\n\t// kubernetes doc flag\n\tAnalyzeCmd.Flags().BoolVarP(&withDoc, \"with-doc\", \"d\", false, \"Give me the official documentation of the involved field\")\n\t// interactive mode flag\n\tAnalyzeCmd.Flags().BoolVarP(&interactiveMode, \"interactive\", \"i\", false, \"Enable interactive mode that allows further conversation with LLM about the problem. Works only with --explain flag\")\n\t// custom analysis flag\n\tAnalyzeCmd.Flags().BoolVarP(&customAnalysis, \"custom-analysis\", \"z\", false, \"Enable custom analyzers\")\n\t// add custom headers flag\n\tAnalyzeCmd.Flags().StringSliceVarP(&customHeaders, \"custom-headers\", \"r\", []string{}, \"Custom Headers, <key>:<value> (e.g CustomHeaderKey:CustomHeaderValue AnotherHeader:AnotherValue)\")\n\t// label selector flag\n\tAnalyzeCmd.Flags().StringVarP(&labelSelector, \"selector\", \"L\", \"\", \"Label selector (label query) to filter on, supports '=', '==', and '!='. (e.g. -L key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.\")\n\t// print stats\n\tAnalyzeCmd.Flags().BoolVarP(&withStats, \"with-stat\", \"s\", false, \"Print analysis stats. This option disables errors display.\")\n}\n"
  },
  {
    "path": "cmd/auth/add.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"golang.org/x/term\"\n)\n\nconst (\n\tdefaultBackend = \"openai\"\n\tdefaultModel   = \"gpt-4o\"\n)\n\nvar addCmd = &cobra.Command{\n\tUse:   \"add\",\n\tShort: \"Add new provider\",\n\tLong:  \"The add command allows to configure a new backend AI provider\",\n\tPreRun: func(cmd *cobra.Command, args []string) {\n\t\tbackend, _ := cmd.Flags().GetString(\"backend\")\n\t\tif strings.ToLower(backend) == \"azureopenai\" {\n\t\t\t_ = cmd.MarkFlagRequired(\"engine\")\n\t\t\t_ = cmd.MarkFlagRequired(\"baseurl\")\n\t\t}\n\t\tif strings.ToLower(backend) == \"amazonsagemaker\" {\n\t\t\t_ = cmd.MarkFlagRequired(\"endpointname\")\n\t\t\t_ = cmd.MarkFlagRequired(\"providerRegion\")\n\t\t}\n\t\tif strings.ToLower(backend) == \"amazonbedrock\" {\n\t\t\t_ = cmd.MarkFlagRequired(\"providerRegion\")\n\t\t}\n\t\tif strings.ToLower(backend) == \"ibmwatsonxai\" {\n\t\t\t_ = cmd.MarkFlagRequired(\"providerId\")\n\t\t}\n\t},\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\tvalidBackend := func(validBackends []string, backend string) bool {\n\t\t\tfor _, b := range validBackends {\n\t\t\t\tif b == backend {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\n\t\t// check if backend is not empty and a valid value\n\t\tif backend == \"\" {\n\t\t\tcolor.Yellow(fmt.Sprintf(\"Warning: backend input is empty, will use the default value: %s\", defaultBackend))\n\t\t\tbackend = defaultBackend\n\t\t} else {\n\t\t\tif !validBackend(ai.Backends, backend) {\n\t\t\t\tcolor.Red(\"Error: Backend AI accepted values are '%v'\", strings.Join(ai.Backends, \", \"))\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\t// get ai configuration\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// search for provider with same name\n\t\tproviderIndex := -1\n\t\tfor i, provider := range configAI.Providers {\n\t\t\tif backend == provider.Name {\n\t\t\t\tproviderIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif providerIndex != -1 {\n\t\t\t// provider with same name exists, update provider info\n\t\t\tcolor.Yellow(\"Provider with same name already exists.\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// check if model is not empty\n\t\tif model == \"\" {\n\t\t\tmodel = defaultModel\n\t\t\tcolor.Yellow(fmt.Sprintf(\"Warning: model input is empty, will use the default value: %s\", defaultModel))\n\t\t}\n\t\tif temperature > 1.0 || temperature < 0.0 {\n\t\t\tcolor.Red(\"Error: temperature ranges from 0 to 1.\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif topP > 1.0 || topP < 0.0 {\n\t\t\tcolor.Red(\"Error: topP ranges from 0 to 1.\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif topK < 1 || topK > 100 {\n\t\t\tcolor.Red(\"Error: topK ranges from 1 to 100.\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tif ai.NeedPassword(backend) && password == \"\" {\n\t\t\tfmt.Printf(\"Enter %s Key: \", backend)\n\t\t\tbytePassword, err := term.ReadPassword(int(syscall.Stdin))\n\t\t\tif err != nil {\n\t\t\t\tcolor.Red(\"Error reading %s Key from stdin: %s\", backend,\n\t\t\t\t\terr.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tpassword = strings.TrimSpace(string(bytePassword))\n\t\t}\n\n\t\t// create new provider object\n\t\tnewProvider := ai.AIProvider{\n\t\t\tName:           backend,\n\t\t\tModel:          model,\n\t\t\tPassword:       password,\n\t\t\tBaseURL:        baseURL,\n\t\t\tEndpointName:   endpointName,\n\t\t\tEngine:         engine,\n\t\t\tTemperature:    temperature,\n\t\t\tProviderRegion: providerRegion,\n\t\t\tProviderId:     providerId,\n\t\t\tCompartmentId:  compartmentId,\n\t\t\tTopP:           topP,\n\t\t\tTopK:           topK,\n\t\t\tMaxTokens:      maxTokens,\n\t\t\tOrganizationId: organizationId,\n\t\t}\n\n\t\tif providerIndex == -1 {\n\t\t\t// provider with same name does not exist, add new provider to list\n\t\t\tconfigAI.Providers = append(configAI.Providers, newProvider)\n\t\t\tviper.Set(\"ai\", configAI)\n\t\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tcolor.Green(\"%s added to the AI backend provider list\", backend)\n\t\t}\n\t},\n}\n\nfunc init() {\n\t// add flag for backend\n\taddCmd.Flags().StringVarP(&backend, \"backend\", \"b\", defaultBackend, \"Backend AI provider\")\n\t// add flag for model\n\taddCmd.Flags().StringVarP(&model, \"model\", \"m\", defaultModel, \"Backend AI model\")\n\t// add flag for password\n\taddCmd.Flags().StringVarP(&password, \"password\", \"p\", \"\", \"Backend AI password\")\n\t// add flag for url\n\taddCmd.Flags().StringVarP(&baseURL, \"baseurl\", \"u\", \"\", \"URL AI provider, (e.g `http://localhost:8080/v1`)\")\n\t// add flag for endpointName\n\taddCmd.Flags().StringVarP(&endpointName, \"endpointname\", \"n\", \"\", \"Endpoint Name, e.g. `endpoint-xxxxxxxxxxxx` (only for amazonbedrock, amazonsagemaker backends)\")\n\t// add flag for topP\n\taddCmd.Flags().Float32VarP(&topP, \"topp\", \"\", 0.5, \"Probability Cutoff: Set a threshold (0.0-1.0) to limit word choices. Higher values add randomness, lower values increase predictability.\")\n\t// add flag for topK\n\taddCmd.Flags().Int32VarP(&topK, \"topk\", \"c\", 50, \"Sampling Cutoff: Set a threshold (1-100) to restrict the sampling process to the top K most probable words at each step. Higher values lead to greater variability, lower values increases predictability.\")\n\t// max tokens\n\taddCmd.Flags().IntVarP(&maxTokens, \"maxtokens\", \"l\", 2048, \"Specify a maximum output length. Adjust (1-...) to control text length. Higher values produce longer output, lower values limit length\")\n\t// add flag for temperature\n\taddCmd.Flags().Float32VarP(&temperature, \"temperature\", \"t\", 0.7, \"The sampling temperature, value ranges between 0 ( output be more deterministic) and 1 (more random)\")\n\t// add flag for azure open ai engine/deployment name\n\taddCmd.Flags().StringVarP(&engine, \"engine\", \"e\", \"\", \"Azure AI deployment name (only for azureopenai backend)\")\n\t//add flag for amazonbedrock region name\n\taddCmd.Flags().StringVarP(&providerRegion, \"providerRegion\", \"r\", \"\", \"Provider Region name (only for amazonbedrock, googlevertexai backend)\")\n\t//add flag for vertexAI/WatsonxAI Project ID\n\taddCmd.Flags().StringVarP(&providerId, \"providerId\", \"i\", \"\", \"Provider specific ID for e.g. project (only for googlevertexai/ibmwatsonxai backend)\")\n\t//add flag for OCI Compartment ID\n\taddCmd.Flags().StringVarP(&compartmentId, \"compartmentId\", \"k\", \"\", \"Compartment ID for generative AI model (only for oci backend)\")\n\t// add flag for openai organization\n\taddCmd.Flags().StringVarP(&organizationId, \"organizationId\", \"o\", \"\", \"OpenAI or AzureOpenAI Organization ID (only for openai and azureopenai backend)\")\n}\n"
  },
  {
    "path": "cmd/auth/auth.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tbackend        string\n\tpassword       string\n\tbaseURL        string\n\tendpointName   string\n\tmodel          string\n\tengine         string\n\ttemperature    float32\n\tproviderRegion string\n\tproviderId     string\n\tcompartmentId  string\n\ttopP           float32\n\ttopK           int32\n\tmaxTokens      int\n\torganizationId string\n)\n\nvar configAI ai.AIConfiguration\n\n// authCmd represents the auth command\nvar AuthCmd = &cobra.Command{\n\tUse:   \"auth\",\n\tShort: \"Authenticate with your chosen backend\",\n\tLong:  `Provide the necessary credentials to authenticate with your chosen backend.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif len(args) == 0 {\n\t\t\t_ = cmd.Help()\n\t\t\treturn\n\t\t}\n\t},\n}\n\nfunc init() {\n\t// add subcommand to list backends\n\tAuthCmd.AddCommand(listCmd)\n\t// add subcommand to create new backend provider\n\tAuthCmd.AddCommand(addCmd)\n\t// add subcommand to remove new backend provider\n\tAuthCmd.AddCommand(removeCmd)\n\t// add subcommand to set default backend provider\n\tAuthCmd.AddCommand(defaultCmd)\n\t// add subcommand to update backend provider\n\tAuthCmd.AddCommand(updateCmd)\n}\n"
  },
  {
    "path": "cmd/auth/default.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tproviderName string\n)\n\nvar defaultCmd = &cobra.Command{\n\tUse:   \"default\",\n\tShort: \"Set your default AI backend provider\",\n\tLong:  \"The command to set your new default AI backend provider (default is openai)\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif providerName == \"\" {\n\t\t\tif configAI.DefaultProvider != \"\" {\n\t\t\t\tcolor.Yellow(\"Your default provider is %s\", configAI.DefaultProvider)\n\t\t\t} else {\n\t\t\t\tcolor.Yellow(\"Your default provider is openai\")\n\t\t\t}\n\t\t\tos.Exit(0)\n\t\t}\n\t\t// lowercase the provider name\n\t\tproviderName = strings.ToLower(providerName)\n\n\t\t// Check if the provider is in the provider list\n\t\tproviderExists := false\n\t\tfor _, provider := range configAI.Providers {\n\t\t\tif provider.Name == providerName {\n\t\t\t\tproviderExists = true\n\t\t\t}\n\t\t}\n\t\tif !providerExists {\n\t\t\tcolor.Red(\"Error: Provider %s does not exist\", providerName)\n\t\t\tos.Exit(1)\n\t\t}\n\t\t// Set the default provider\n\t\tconfigAI.DefaultProvider = providerName\n\n\t\tviper.Set(\"ai\", configAI)\n\t\t// Viper write config\n\t\terr = viper.WriteConfig()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\t// Print acknowledgement\n\t\tcolor.Green(\"Default provider set to %s\", providerName)\n\t},\n}\n\nfunc init() {\n\t// provider name flag\n\tdefaultCmd.Flags().StringVarP(&providerName, \"provider\", \"p\", \"\", \"The name of the provider to set as default\")\n}\n"
  },
  {
    "path": "cmd/auth/list.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar details bool\n\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List configured providers\",\n\tLong:  \"The list command displays a list of configured providers\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// get ai configuration\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Print the default if it is set\n\t\tfmt.Print(color.YellowString(\"Default: \\n\"))\n\t\tif configAI.DefaultProvider != \"\" {\n\t\t\tfmt.Printf(\"> %s\\n\", color.BlueString(configAI.DefaultProvider))\n\t\t} else {\n\t\t\tfmt.Printf(\"> %s\\n\", color.BlueString(\"openai\"))\n\t\t}\n\n\t\t// Get list of all AI Backends and only print them if they are not in the provider list\n\t\tfmt.Print(color.YellowString(\"Active: \\n\"))\n\t\tfor _, aiBackend := range ai.Backends {\n\t\t\tproviderExists := false\n\t\t\tfor _, provider := range configAI.Providers {\n\t\t\t\tif provider.Name == aiBackend {\n\t\t\t\t\tproviderExists = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif providerExists {\n\t\t\t\tfmt.Printf(\"> %s\\n\", color.GreenString(aiBackend))\n\t\t\t\tif details {\n\t\t\t\t\tfor _, provider := range configAI.Providers {\n\t\t\t\t\t\tif provider.Name == aiBackend {\n\t\t\t\t\t\t\tprintDetails(provider)\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\tfmt.Print(color.YellowString(\"Unused: \\n\"))\n\t\tfor _, aiBackend := range ai.Backends {\n\t\t\tproviderExists := false\n\t\t\tfor _, provider := range configAI.Providers {\n\t\t\t\tif provider.Name == aiBackend {\n\t\t\t\t\tproviderExists = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !providerExists {\n\t\t\t\tfmt.Printf(\"> %s\\n\", color.RedString(aiBackend))\n\t\t\t}\n\t\t}\n\t},\n}\n\nfunc init() {\n\tlistCmd.Flags().BoolVar(&details, \"details\", false, \"Print active provider configuration details\")\n}\n\nfunc printDetails(provider ai.AIProvider) {\n\tif provider.Model != \"\" {\n\t\tfmt.Printf(\"   - Model: %s\\n\", provider.Model)\n\t}\n\tif provider.Engine != \"\" {\n\t\tfmt.Printf(\"   - Engine: %s\\n\", provider.Engine)\n\t}\n\tif provider.BaseURL != \"\" {\n\t\tfmt.Printf(\"   - BaseURL: %s\\n\", provider.BaseURL)\n\t}\n}\n"
  },
  {
    "path": "cmd/auth/remove.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar removeCmd = &cobra.Command{\n\tUse:   \"remove\",\n\tShort: \"Remove provider(s)\",\n\tLong:  \"The command to remove AI backend provider(s)\",\n\tPreRun: func(cmd *cobra.Command, args []string) {\n\t\t_ = cmd.MarkFlagRequired(\"backends\")\n\t},\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif backend == \"\" {\n\t\t\tcolor.Red(\"Error: backends must be set.\")\n\t\t\t_ = cmd.Help()\n\t\t\treturn\n\t\t}\n\t\tinputBackends := strings.Split(backend, \",\")\n\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfor _, b := range inputBackends {\n\t\t\tfoundBackend := false\n\t\t\tfor i, provider := range configAI.Providers {\n\t\t\t\tif b == provider.Name {\n\t\t\t\t\tfoundBackend = true\n\t\t\t\t\tconfigAI.Providers = append(configAI.Providers[:i], configAI.Providers[i+1:]...)\n\t\t\t\t\tif configAI.DefaultProvider == b {\n\t\t\t\t\t\tconfigAI.DefaultProvider = \"openai\"\n\t\t\t\t\t}\n\t\t\t\t\tcolor.Green(\"%s deleted from the AI backend provider list\", b)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !foundBackend {\n\t\t\t\tcolor.Red(\"Error: %s does not exist in configuration file. Please use k8sgpt auth new.\", b)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\tviper.Set(\"ai\", configAI)\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\n\t},\n}\n\nfunc init() {\n\t// add flag for backends\n\tremoveCmd.Flags().StringVarP(&backend, \"backends\", \"b\", \"\", \"Backend AI providers to remove (separated by a comma)\")\n}\n"
  },
  {
    "path": "cmd/auth/update.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 auth\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar updateCmd = &cobra.Command{\n\tUse:   \"update\",\n\tShort: \"Update a backend provider\",\n\tLong:  \"The command to update an AI backend provider\",\n\t// Args:  cobra.ExactArgs(1),\n\tPreRun: func(cmd *cobra.Command, args []string) {\n\t\t_ = cmd.MarkFlagRequired(\"backend\")\n\t\tbackend, _ := cmd.Flags().GetString(\"backend\")\n\t\tif strings.ToLower(backend) == \"azureopenai\" {\n\t\t\t_ = cmd.MarkFlagRequired(\"engine\")\n\t\t\t_ = cmd.MarkFlagRequired(\"baseurl\")\n\t\t}\n\t\torganizationId, _ := cmd.Flags().GetString(\"organizationId\")\n\t\tif strings.ToLower(backend) != \"azureopenai\" && strings.ToLower(backend) != \"openai\" {\n\t\t\tif organizationId != \"\" {\n\t\t\t\tcolor.Red(\"Error: organizationId must be empty for backends other than azureopenai or openai.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\t},\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// get ai configuration\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tbackend, _ := cmd.Flags().GetString(\"backend\")\n\n\t\tif temperature > 1.0 || temperature < 0.0 {\n\t\t\tcolor.Red(\"Error: temperature ranges from 0 to 1.\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tfoundBackend := false\n\t\tfor i, provider := range configAI.Providers {\n\t\t\tif backend == provider.Name {\n\t\t\t\tfoundBackend = true\n\t\t\t\tif backend != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].Name = backend\n\t\t\t\t\tcolor.Blue(\"Backend name updated successfully\")\n\t\t\t\t}\n\t\t\t\tif model != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].Model = model\n\t\t\t\t\tcolor.Blue(\"Model updated successfully\")\n\t\t\t\t}\n\t\t\t\tif password != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].Password = password\n\t\t\t\t\tcolor.Blue(\"Password updated successfully\")\n\t\t\t\t}\n\t\t\t\tif baseURL != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].BaseURL = baseURL\n\t\t\t\t\tcolor.Blue(\"Base URL updated successfully\")\n\t\t\t\t}\n\t\t\t\tif engine != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].Engine = engine\n\t\t\t\t}\n\t\t\t\tif organizationId != \"\" {\n\t\t\t\t\tconfigAI.Providers[i].OrganizationId = organizationId\n\t\t\t\t\tcolor.Blue(\"Organization Id updated successfully\")\n\t\t\t\t}\n\t\t\t\tconfigAI.Providers[i].Temperature = temperature\n\t\t\t\tcolor.Green(\"%s updated in the AI backend provider list\", backend)\n\t\t\t}\n\t\t}\n\t\tif !foundBackend {\n\t\t\tcolor.Red(\"Error: %s does not exist in configuration file. Please use k8sgpt auth new.\", backend)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tviper.Set(\"ai\", configAI)\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t},\n}\n\nfunc init() {\n\t// update flag for backend\n\tupdateCmd.Flags().StringVarP(&backend, \"backend\", \"b\", \"\", \"Update backend AI provider\")\n\t// update flag for model\n\tupdateCmd.Flags().StringVarP(&model, \"model\", \"m\", \"\", \"Update backend AI model\")\n\t// update flag for password\n\tupdateCmd.Flags().StringVarP(&password, \"password\", \"p\", \"\", \"Update backend AI password\")\n\t// update flag for url\n\tupdateCmd.Flags().StringVarP(&baseURL, \"baseurl\", \"u\", \"\", \"Update URL AI provider, (e.g `http://localhost:8080/v1`)\")\n\t// add flag for temperature\n\tupdateCmd.Flags().Float32VarP(&temperature, \"temperature\", \"t\", 0.7, \"The sampling temperature, value ranges between 0 ( output be more deterministic) and 1 (more random)\")\n\t// update flag for azure open ai engine/deployment name\n\tupdateCmd.Flags().StringVarP(&engine, \"engine\", \"e\", \"\", \"Update Azure AI deployment name\")\n\t// update flag for organizationId\n\tupdateCmd.Flags().StringVarP(&organizationId, \"organizationId\", \"o\", \"\", \"Update OpenAI or Azure organization Id\")\n}\n"
  },
  {
    "path": "cmd/cache/add.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tregion string\n\t//nolint:unused\n\tbucketName     string\n\tstorageAccount string\n\tcontainerName  string\n\tprojectId      string\n\tendpoint       string\n\tinsecure       bool\n)\n\n// addCmd represents the add command\nvar addCmd = &cobra.Command{\n\tUse:   \"add [cache type]\",\n\tShort: \"Add a remote cache\",\n\tLong: `This command allows you to add a remote cache to store the results of an analysis.\n\tThe supported cache types are:\n\t- Azure Blob storage (e.g., k8sgpt cache add azure)\n\t- Google Cloud storage (e.g., k8sgpt cache add gcs)\n\t- S3 (e.g., k8sgpt cache add s3)\n\t- Interplex (e.g., k8sgpt cache add interplex)`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif len(args) == 0 {\n\t\t\tcolor.Red(\"Error: Please provide a value for cache types. Run k8sgpt cache add --help\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfmt.Println(color.YellowString(\"Adding remote based cache\"))\n\t\tcacheType := args[0]\n\t\tremoteCache, err := cache.NewCacheProvider(strings.ToLower(cacheType), bucketName, region, endpoint, storageAccount, containerName, projectId, insecure)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\terr = cache.AddRemoteCache(remoteCache)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t},\n}\n\nfunc init() {\n\tCacheCmd.AddCommand(addCmd)\n\taddCmd.Flags().StringVarP(&region, \"region\", \"r\", \"us-east-1\", \"The region to use for the AWS S3 or GCS cache\")\n\taddCmd.Flags().StringVarP(&endpoint, \"endpoint\", \"e\", \"\", \"The S3 or minio endpoint\")\n\taddCmd.Flags().BoolVarP(&insecure, \"insecure\", \"i\", false, \"Skip TLS verification for S3/Minio custom endpoint\")\n\taddCmd.Flags().StringVarP(&bucketName, \"bucket\", \"b\", \"\", \"The name of the AWS S3 bucket to use for the cache\")\n\taddCmd.Flags().StringVarP(&projectId, \"projectid\", \"p\", \"\", \"The GCP project ID\")\n\taddCmd.Flags().StringVarP(&storageAccount, \"storageacc\", \"s\", \"\", \"The Azure storage account name of the container\")\n\taddCmd.Flags().StringVarP(&containerName, \"container\", \"c\", \"\", \"The Azure container name to use for the cache\")\n\taddCmd.MarkFlagsRequiredTogether(\"storageacc\", \"container\")\n\t// Tedious check to ensure we don't include arguments from different providers\n\taddCmd.MarkFlagsMutuallyExclusive(\"region\", \"storageacc\")\n\taddCmd.MarkFlagsMutuallyExclusive(\"region\", \"container\")\n\taddCmd.MarkFlagsMutuallyExclusive(\"bucket\", \"storageacc\")\n\taddCmd.MarkFlagsMutuallyExclusive(\"bucket\", \"container\")\n\taddCmd.MarkFlagsMutuallyExclusive(\"projectid\", \"storageacc\")\n\taddCmd.MarkFlagsMutuallyExclusive(\"projectid\", \"container\")\n}\n"
  },
  {
    "path": "cmd/cache/cache.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\n// cacheCmd represents the cache command\nvar CacheCmd = &cobra.Command{\n\tUse:   \"cache\",\n\tShort: \"For working with the cache the results of an analysis\",\n\tLong:  `Cache commands allow you to add a remote cache, list the contents of the cache, and remove items from the cache.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := cmd.Help()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t},\n}\n\nfunc init() {\n}\n"
  },
  {
    "path": "cmd/cache/get.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"fmt\"\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/spf13/cobra\"\n\t\"os\"\n)\n\n// listCmd represents the list command\nvar getCmd = &cobra.Command{\n\tUse:   \"get\",\n\tShort: \"Get the current cache\",\n\tLong:  `Returns the current remote cache being used`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// load remote cache if it is configured\n\t\tc, err := cache.GetCacheConfiguration()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfmt.Printf(\"Current remote cache is: %s\", c.GetName())\n\t},\n}\n\nfunc init() {\n\tCacheCmd.AddCommand(getCmd)\n\n}\n"
  },
  {
    "path": "cmd/cache/list.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"os\"\n\t\"reflect\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/spf13/cobra\"\n)\n\n// listCmd represents the list command\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List the contents of the cache\",\n\tLong:  `This command allows you to list the contents of the cache.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// load remote cache if it is configured\n\t\tc, err := cache.GetCacheConfiguration()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tnames, err := c.List()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tvar headers []string\n\t\tobj := cache.CacheObjectDetails{}\n\t\tobjType := reflect.TypeOf(obj)\n\t\tfor i := 0; i < objType.NumField(); i++ {\n\t\t\tfield := objType.Field(i)\n\t\t\theaders = append(headers, field.Name)\n\t\t}\n\n\t\ttable := tablewriter.NewWriter(os.Stdout)\n\t\ttable.SetHeader(headers)\n\n\t\tfor _, v := range names {\n\t\t\ttable.Append([]string{v.Name, v.UpdatedAt.String()})\n\t\t}\n\t\ttable.Render()\n\t},\n}\n\nfunc init() {\n\tCacheCmd.AddCommand(listCmd)\n\n}\n"
  },
  {
    "path": "cmd/cache/purge.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar all bool\n\nvar purgeCmd = &cobra.Command{\n\tUse:   \"purge [object name]\",\n\tShort: \"Purge a remote cache\",\n\tLong:  \"This command allows you to delete/purge one object from the cache or all objects with --all flag.\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tc, err := cache.GetCacheConfiguration()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tif all {\n\t\t\tfmt.Println(color.YellowString(\"Purging all objects from the remote cache.\"))\n\t\t\tnames, err := c.List()\n\t\t\tif err != nil {\n\t\t\t\tcolor.Red(\"Error listing cache objects: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif len(names) == 0 {\n\t\t\t\tfmt.Println(color.GreenString(\"No objects to delete.\"))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvar failed []string\n\t\t\tfor _, obj := range names {\n\t\t\t\terr := c.Remove(obj.Name)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfailed = append(failed, obj.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(failed) > 0 {\n\t\t\t\tcolor.Red(\"Failed to delete: %v\", failed)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tfmt.Println(color.GreenString(\"All objects deleted.\"))\n\t\t\treturn\n\t\t}\n\n\t\tif len(args) == 0 {\n\t\t\tcolor.Red(\"Error: Please provide a value for object name or use --all. Run k8sgpt cache purge --help\")\n\t\t\tos.Exit(1)\n\t\t}\n\t\tobjectKey := args[0]\n\t\tfmt.Println(color.YellowString(\"Purging a remote cache.\"))\n\t\terr = c.Remove(objectKey)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tfmt.Println(color.GreenString(\"Object deleted.\"))\n\t},\n}\n\nfunc init() {\n\tpurgeCmd.Flags().BoolVar(&all, \"all\", false, \"Purge all objects in the cache\")\n\tCacheCmd.AddCommand(purgeCmd)\n}\n"
  },
  {
    "path": "cmd/cache/remove.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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\n\thttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\npackage cache\n\nimport (\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/spf13/cobra\"\n)\n\n// removeCmd represents the remove command\nvar removeCmd = &cobra.Command{\n\tUse:   \"remove\",\n\tShort: \"Remove the remote cache\",\n\tLong:  `This command allows you to remove the remote cache and use the default filecache.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\terr := cache.RemoveRemoteCache()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcolor.Green(\"Successfully removed the remote cache\")\n\t},\n}\n\nfunc init() {\n\tCacheCmd.AddCommand(removeCmd)\n}\n"
  },
  {
    "path": "cmd/customAnalyzer/add.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 customanalyzer\n\nimport (\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\tcustomAnalyzer \"github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tname string\n\turl  string\n\tport int\n)\n\nvar addCmd = &cobra.Command{\n\tUse:     \"add\",\n\tAliases: []string{\"add\"},\n\tShort:   \"This command will add a custom analyzer from source\",\n\tLong:    \"This command allows you to add/remote/list an existing custom analyzer.\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\terr := viper.UnmarshalKey(\"custom_analyzers\", &configCustomAnalyzer)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tanalyzer := customAnalyzer.NewCustomAnalyzer()\n\n\t\t// Check if configuration is valid\n\t\terr = analyzer.Check(configCustomAnalyzer, name, url, port)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error adding custom analyzer: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tconfigCustomAnalyzer = append(configCustomAnalyzer, customAnalyzer.CustomAnalyzerConfiguration{\n\t\t\tName: name,\n\t\t\tConnection: customAnalyzer.Connection{\n\t\t\t\tUrl:  url,\n\t\t\t\tPort: port,\n\t\t\t},\n\t\t})\n\n\t\tviper.Set(\"custom_analyzers\", configCustomAnalyzer)\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcolor.Green(\"%s added to the custom analyzers config list\", name)\n\n\t},\n}\n\nfunc init() {\n\taddCmd.Flags().StringVarP(&name, \"name\", \"n\", \"my-custom-analyzer\", \"Name of the custom analyzer.\")\n\taddCmd.Flags().StringVarP(&url, \"url\", \"u\", \"localhost\", \"URL for the custom analyzer connection.\")\n\taddCmd.Flags().IntVarP(&port, \"port\", \"r\", 8085, \"Port for the custom analyzer connection.\")\n}\n"
  },
  {
    "path": "cmd/customAnalyzer/customAnalyzer.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 customanalyzer\n\nimport (\n\tcustomAnalyzer \"github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer\"\n\t\"github.com/spf13/cobra\"\n)\n\nvar configCustomAnalyzer []customAnalyzer.CustomAnalyzerConfiguration\n\n// authCmd represents the auth command\nvar CustomAnalyzerCmd = &cobra.Command{\n\tUse:   \"custom-analyzer\",\n\tShort: \"Manage a custom analyzer\",\n\tLong:  `This command allows you to manage custom analyzers, including adding, removing, and listing them.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif len(args) == 0 {\n\t\t\t_ = cmd.Help()\n\t\t\treturn\n\t\t}\n\t},\n}\n\nfunc init() {\n\t// add subcommand to add custom analyzer\n\tCustomAnalyzerCmd.AddCommand(addCmd)\n\t// remove subcomment to remove custom analyzer\n\tCustomAnalyzerCmd.AddCommand(removeCmd)\n\t// list subcomment to list custom analyzer\n\tCustomAnalyzerCmd.AddCommand(listCmd)\n}\n"
  },
  {
    "path": "cmd/customAnalyzer/list.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 customanalyzer\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\tcustomAnalyzer \"github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar details bool\n\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List configured custom analyzers\",\n\tLong:  \"The list command displays a list of configured custom analyzers\",\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// get custom_analyzers configuration\n\t\terr := viper.UnmarshalKey(\"custom_analyzers\", &configCustomAnalyzer)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Get list of all Custom Analyers configured\n\t\tfmt.Print(color.YellowString(\"Active: \\n\"))\n\t\tfor _, analyzer := range configCustomAnalyzer {\n\t\t\tfmt.Printf(\"> %s\\n\", color.GreenString(analyzer.Name))\n\t\t\tif details {\n\t\t\t\tprintDetails(analyzer)\n\t\t\t}\n\t\t}\n\t},\n}\n\nfunc init() {\n\tlistCmd.Flags().BoolVar(&details, \"details\", false, \"Print custom analyzers configuration details\")\n}\n\nfunc printDetails(analyzer customAnalyzer.CustomAnalyzerConfiguration) {\n\tfmt.Printf(\"   - Url: %s\\n\", analyzer.Connection.Url)\n\tfmt.Printf(\"   - Port: %d\\n\", analyzer.Connection.Port)\n\n}\n"
  },
  {
    "path": "cmd/customAnalyzer/remove.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 customanalyzer\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tnames string\n)\n\nvar removeCmd = &cobra.Command{\n\tUse:   \"remove\",\n\tShort: \"Remove custom analyzer(s)\",\n\tLong:  \"The command to remove custom analyzer(s)\",\n\tPreRun: func(cmd *cobra.Command, args []string) {\n\t\t// Ensure that the \"names\" flag is provided before running the command\n\t\t_ = cmd.MarkFlagRequired(\"names\")\n\t},\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif names == \"\" {\n\t\t\t// Display an error message and show command help if \"names\" is not set\n\t\t\tcolor.Red(\"Error: names must be set.\")\n\t\t\t_ = cmd.Help()\n\t\t\treturn\n\t\t}\n\t\t// Split the provided names by comma\n\t\tinputCustomAnalyzers := strings.Split(names, \",\")\n\n\t\t// Load the custom analyzers from the configuration file\n\t\terr := viper.UnmarshalKey(\"custom_analyzers\", &configCustomAnalyzer)\n\t\tif err != nil {\n\t\t\t// Display an error message if the configuration cannot be loaded\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Iterate over each input analyzer name\n\t\tfor _, inputAnalyzer := range inputCustomAnalyzers {\n\t\t\tfoundAnalyzer := false\n\t\t\t// Search for the analyzer in the current configuration\n\t\t\tfor i, analyzer := range configCustomAnalyzer {\n\t\t\t\tif analyzer.Name == inputAnalyzer {\n\t\t\t\t\tfoundAnalyzer = true\n\n\t\t\t\t\t// Remove the analyzer from the configuration list\n\t\t\t\t\tconfigCustomAnalyzer = append(configCustomAnalyzer[:i], configCustomAnalyzer[i+1:]...)\n\t\t\t\t\tcolor.Green(\"%s deleted from the custom analyzer list\", analyzer.Name)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !foundAnalyzer {\n\t\t\t\t// Display an error if the analyzer is not found in the configuration\n\t\t\t\tcolor.Red(\"Error: %s does not exist in configuration file. Please use k8sgpt custom-analyzer add.\", inputAnalyzer)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\t// Save the updated configuration back to the file\n\t\tviper.Set(\"custom_analyzers\", configCustomAnalyzer)\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\t// Display an error if the configuration cannot be written\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\n\t},\n}\n\nfunc init() {\n\t// add flag for names\n\tremoveCmd.Flags().StringVarP(&names, \"names\", \"n\", \"\", \"Custom analyzers to remove (separated by a comma)\")\n}\n"
  },
  {
    "path": "cmd/dump/dump.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 dump\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"k8s.io/apimachinery/pkg/version\"\n)\n\ntype K8sGPTInfo struct {\n\tVersion string\n\tCommit  string\n\tDate    string\n}\ntype DumpOut struct {\n\tAIConfiguration        ai.AIConfiguration\n\tActiveFilters          []string\n\tKubenetesServerVersion *version.Info\n\tK8sGPTInfo             K8sGPTInfo\n}\n\nvar DumpCmd = &cobra.Command{\n\tUse:   \"dump\",\n\tShort: \"Creates a dumpfile for debugging issues with K8sGPT\",\n\tLong:  `The dump command will create a dump.*.json which will contain K8sGPT non-sensitive configuration information.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\t// Fetch the configuration object(s)\n\t\t// get ai configuration\n\t\tvar configAI ai.AIConfiguration\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tvar newProvider []ai.AIProvider\n\t\tfor _, config := range configAI.Providers {\n\t\t\t// we blank out the custom headers for data protection reasons\n\t\t\tconfig.CustomHeaders = make([]http.Header, 0)\n\t\t\t// blank out the password\n\t\t\tif len(config.Password) > 4 {\n\t\t\t\tconfig.Password = config.Password[:4] + \"***\"\n\t\t\t} else {\n\t\t\t\t// If the password is shorter than 4 characters\n\t\t\t\tconfig.Password = \"***\"\n\t\t\t}\n\t\t\tnewProvider = append(newProvider, config)\n\t\t}\n\t\tconfigAI.Providers = newProvider\n\t\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\t\tkubecontext := viper.GetString(\"kubecontext\")\n\t\tkubeconfig := viper.GetString(\"kubeconfig\")\n\t\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tv, err := client.Client.Discovery().ServerVersion()\n\t\tif err != nil {\n\t\t\tcolor.Yellow(\"Could not find kubernetes server version\")\n\t\t}\n\t\tvar dumpOut DumpOut = DumpOut{\n\t\t\tAIConfiguration:        configAI,\n\t\t\tActiveFilters:          activeFilters,\n\t\t\tKubenetesServerVersion: v,\n\t\t\tK8sGPTInfo: K8sGPTInfo{\n\t\t\t\tVersion: viper.GetString(\"Version\"),\n\t\t\t\tCommit:  viper.GetString(\"Commit\"),\n\t\t\t\tDate:    viper.GetString(\"Date\"),\n\t\t\t},\n\t\t}\n\t\t// Serialize dumpOut to JSON\n\t\tjsonData, err := json.MarshalIndent(dumpOut, \"\", \" \")\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\t// Write JSON data to file\n\t\tf := fmt.Sprintf(\"dump_%s.json\", time.Now().Format(\"20060102150405\"))\n\t\terr = os.WriteFile(f, jsonData, 0644)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcolor.Green(\"Dump created successfully: %s\", f)\n\t},\n}\n\nfunc init() {\n\n}\n"
  },
  {
    "path": "cmd/filters/add.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 filters\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar addCmd = &cobra.Command{\n\tUse:   \"add [filter(s)]\",\n\tShort: \"Adds one or more new filters.\",\n\tLong:  `The add command adds one or more new filters to the default set of filters used by the analyze.`,\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tinputFilters := strings.Split(args[0], \",\")\n\t\tcoreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()\n\n\t\tavailableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)\n\t\t// Verify filter exist\n\t\tinvalidFilters := []string{}\n\t\tfor _, f := range inputFilters {\n\t\t\tif f == \"\" {\n\t\t\t\tcolor.Red(\"Filter cannot be empty. Please use correct syntax.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tfoundFilter := false\n\t\t\tfor _, filter := range availableFilters {\n\t\t\t\tif filter == f {\n\t\t\t\t\tfoundFilter = true\n\n\t\t\t\t\t// WARNING: This is to enable users correctly understand implications\n\t\t\t\t\t// of enabling logs\n\t\t\t\t\tif filter == \"Log\" {\n\t\t\t\t\t\tcolor.Yellow(\"Warning: by enabling logs, you will be sending potentially sensitive data to the AI backend.\")\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !foundFilter {\n\t\t\t\tinvalidFilters = append(invalidFilters, f)\n\t\t\t}\n\t\t}\n\n\t\tif len(invalidFilters) != 0 {\n\t\t\tcolor.Red(\"Filter %s does not exist. Please use k8sgpt filters list\", strings.Join(invalidFilters, \", \"))\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Get defined active_filters\n\t\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\t\tif len(activeFilters) == 0 {\n\t\t\tactiveFilters = coreFilters\n\t\t}\n\n\t\tmergedFilters := append(activeFilters, inputFilters...)\n\n\t\tuniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)\n\n\t\t// Verify dupplicate\n\t\tif len(dupplicatedFilters) != 0 {\n\t\t\tcolor.Red(\"Duplicate filters found: %s\", strings.Join(dupplicatedFilters, \", \"))\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tviper.Set(\"active_filters\", uniqueFilters)\n\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcolor.Green(\"Filter %s added\", strings.Join(inputFilters, \", \"))\n\t},\n}\n"
  },
  {
    "path": "cmd/filters/filters.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 filters\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar FiltersCmd = &cobra.Command{\n\tUse:     \"filters\",\n\tAliases: []string{\"filter\"},\n\tShort:   \"Manage filters for analyzing Kubernetes resources\",\n\tLong: `The filters command allows you to manage filters that are used to analyze Kubernetes resources.\n\tYou can list available filters to analyze resources.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif len(args) == 0 {\n\t\t\t_ = cmd.Help()\n\t\t\treturn\n\t\t}\n\t},\n}\n\nfunc init() {\n\tFiltersCmd.AddCommand(listCmd)\n\tFiltersCmd.AddCommand(addCmd)\n\tFiltersCmd.AddCommand(removeCmd)\n}\n"
  },
  {
    "path": "cmd/filters/list.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 filters\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"List available filters\",\n\tLong:  `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\t\tcoreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()\n\t\tintegration := integration.NewIntegration()\n\t\tavailableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)\n\n\t\tif len(activeFilters) == 0 {\n\t\t\tactiveFilters = coreFilters\n\t\t}\n\t\tinactiveFilters := util.SliceDiff(availableFilters, activeFilters)\n\t\tfmt.Print(color.YellowString(\"Active: \\n\"))\n\t\tfor _, filter := range activeFilters {\n\t\t\t// if the filter is an integration, mark this differently\n\t\t\t// but if the integration is inactive, remove\n\t\t\tif slices.Contains(integrationFilters, filter) {\n\t\t\t\tfmt.Printf(\"> %s\\n\", color.BlueString(\"%s (integration)\", filter))\n\t\t\t} else {\n\t\t\t\t// This strange bit of logic will loop through every integration via\n\t\t\t\t// OwnsAnalyzer subcommand to check the filter and as the integrationFilters...\n\t\t\t\t// was no match, we know this isn't part of an active integration\n\t\t\t\tif _, err := integration.AnalyzerByIntegration(filter); err != nil {\n\t\t\t\t\tfmt.Printf(\"> %s\\n\", color.GreenString(filter))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// display inactive filters\n\t\tif len(inactiveFilters) != 0 {\n\t\t\tfmt.Print(color.YellowString(\"Unused: \\n\"))\n\t\t\tfor _, filter := range inactiveFilters {\n\t\t\t\t// if the filter is an integration, mark this differently\n\t\t\t\tif slices.Contains(integrationFilters, filter) {\n\t\t\t\t\tfmt.Printf(\"> %s\\n\", color.BlueString(\"%s (integration)\", filter))\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Printf(\"> %s\\n\", color.RedString(filter))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n}\n"
  },
  {
    "path": "cmd/filters/remove.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 filters\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar removeCmd = &cobra.Command{\n\tUse:   \"remove [filter(s)]\",\n\tShort: \"Remove one or more filters.\",\n\tLong:  `The add command remove one or more filters to the default set of filters used by the analyze.`,\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tinputFilters := strings.Split(args[0], \",\")\n\n\t\t// Get defined active_filters\n\t\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\t\tcoreFilters, _, _ := analyzer.ListFilters()\n\n\t\tif len(activeFilters) == 0 {\n\t\t\tactiveFilters = coreFilters\n\t\t}\n\n\t\t// Check if input input filters is not empty\n\t\tfor _, f := range inputFilters {\n\t\t\tif f == \"\" {\n\t\t\t\tcolor.Red(\"Filter cannot be empty. Please use correct syntax.\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\n\t\t// verify dupplicate filters example: k8sgpt filters remove Pod Pod\n\t\tuniqueFilters, dupplicatedFilters := util.RemoveDuplicates(inputFilters)\n\t\tif len(dupplicatedFilters) != 0 {\n\t\t\tcolor.Red(\"Duplicate filters found: %s\", strings.Join(dupplicatedFilters, \", \"))\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Verify if filter exist in config file and update default_filter\n\t\tfilterNotFound := []string{}\n\t\tfor _, filter := range uniqueFilters {\n\t\t\tfoundFilter := false\n\t\t\tfor i, f := range activeFilters {\n\t\t\t\tif f == filter {\n\t\t\t\t\tfoundFilter = true\n\t\t\t\t\tactiveFilters = append(activeFilters[:i], activeFilters[i+1:]...)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !foundFilter {\n\t\t\t\tfilterNotFound = append(filterNotFound, filter)\n\t\t\t}\n\t\t}\n\n\t\tif len(filterNotFound) != 0 {\n\t\t\tcolor.Red(\"Filter(s) %s does not exist in configuration file. Please use k8sgpt filters add.\", strings.Join(filterNotFound, \", \"))\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tviper.Set(\"active_filters\", activeFilters)\n\n\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcolor.Green(\"Filter(s) %s removed\", strings.Join(inputFilters, \", \"))\n\t},\n}\n"
  },
  {
    "path": "cmd/generate/generate.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 generate\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"runtime\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tbackend     string\n\tbackendType string\n)\n\n// generateCmd represents the auth command\nvar GenerateCmd = &cobra.Command{\n\tUse:   \"generate\",\n\tShort: \"Generate Key for your chosen backend (opens browser)\",\n\tLong:  `Opens your browser to generate a key for your chosen backend.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\tbackendType = viper.GetString(\"backend_type\")\n\t\tif backendType == \"\" {\n\t\t\t// Set the default backend\n\t\t\tbackend = \"openai\"\n\t\t}\n\t\t// override the default backend if a flag is provided\n\t\tif backend != \"\" {\n\t\t\tbackendType = backend\n\t\t}\n\t\tfmt.Println(\"\")\n\t\topenbrowser(\"https://platform.openai.com/api-keys\")\n\t},\n}\n\nfunc init() {\n\t// add flag for backend\n\tGenerateCmd.Flags().StringVarP(&backend, \"backend\", \"b\", \"openai\", \"Backend AI provider\")\n}\n\nfunc openbrowser(url string) {\n\tvar err error\n\tisGui := true\n\tswitch runtime.GOOS {\n\tcase \"linux\":\n\t\t_, err = exec.LookPath(\"xdg-open\")\n\t\tif err != nil {\n\t\t\tisGui = false\n\t\t} else {\n\t\t\terr = exec.Command(\"xdg-open\", url).Start()\n\t\t}\n\tcase \"windows\":\n\t\terr = exec.Command(\"rundll32\", \"url.dll,FileProtocolHandler\", url).Start()\n\tcase \"darwin\":\n\t\terr = exec.Command(\"open\", url).Start()\n\tdefault:\n\t\terr = fmt.Errorf(\"unsupported platform\")\n\t}\n\tprintInstructions(isGui, backend)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc printInstructions(isGui bool, backendType string) {\n\tfmt.Println(\"\")\n\tif isGui {\n\t\tcolor.Green(\"Opening: https://platform.openai.com/api-keys to generate a key for %s\", backendType)\n\t\tfmt.Println(\"\")\n\t} else {\n\t\tcolor.Green(\"Please open: https://platform.openai.com/api-keys to generate a key for %s\", backendType)\n\t\tfmt.Println(\"\")\n\t}\n\tcolor.Green(\"Please copy the generated key and run `k8sgpt auth add` to add it to your config file\")\n\tfmt.Println(\"\")\n}\n"
  },
  {
    "path": "cmd/integration/activate.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 integration\n\nimport (\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar skipInstall bool\n\n// activateCmd represents the activate command\nvar activateCmd = &cobra.Command{\n\tUse:   \"activate [integration]\",\n\tShort: \"Activate an integration\",\n\tLong:  ``,\n\tArgs:  cobra.ExactArgs(1),\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tintegrationName := args[0]\n\t\tcoreFilters, _, _ := analyzer.ListFilters()\n\n\t\t// Update filters\n\t\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\t\tif len(activeFilters) == 0 {\n\t\t\tactiveFilters = coreFilters\n\t\t}\n\n\t\tintegration := integration.NewIntegration()\n\t\t// Check if the integation exists\n\t\terr := integration.Activate(integrationName, namespace, activeFilters, skipInstall)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tcolor.Green(\"Activated integration %s\", integrationName)\n\t},\n}\n\nfunc init() {\n\tIntegrationCmd.AddCommand(activateCmd)\n\tactivateCmd.Flags().BoolVarP(&skipInstall, \"no-install\", \"s\", false, \"Only activate the integration filter without installing the filter (for example, if that filter plugin is already deployed in cluster, we do not need to re-install it again)\")\n}\n"
  },
  {
    "path": "cmd/integration/deactivate.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 integration\n\nimport (\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/spf13/cobra\"\n)\n\n// deactivateCmd represents the deactivate command\nvar deactivateCmd = &cobra.Command{\n\tUse:   \"deactivate [integration]\",\n\tShort: \"Deactivate an integration\",\n\tArgs:  cobra.ExactArgs(1),\n\tLong:  `For example e.g. k8sgpt integration deactivate prometheus`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tintegrationName := args[0]\n\n\t\tintegration := integration.NewIntegration()\n\n\t\tif err := integration.Deactivate(integrationName, namespace); err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tcolor.Green(\"Deactivated integration %s\", integrationName)\n\n\t},\n}\n\nfunc init() {\n\tIntegrationCmd.AddCommand(deactivateCmd)\n}\n"
  },
  {
    "path": "cmd/integration/integration.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 integration\n\nimport (\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tnamespace string\n)\n\n// IntegrationCmd represents the integrate command\nvar IntegrationCmd = &cobra.Command{\n\tUse:     \"integration\",\n\tAliases: []string{\"integrations\"},\n\tShort:   \"Integrate another tool into K8sGPT\",\n\tLong: `Integrate another tool into K8sGPT. For example:\n\t\n\tk8sgpt integration activate prometheus\n\t\n\tThis would allow you to connect to prometheus running with your cluster.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\t_ = cmd.Help()\n\t},\n}\n\nfunc init() {\n\tIntegrationCmd.PersistentFlags().StringVarP(&namespace, \"namespace\", \"n\", \"default\", \"The namespace to use for the integration\")\n}\n"
  },
  {
    "path": "cmd/integration/list.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 integration\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/spf13/cobra\"\n)\n\n// listCmd represents the list command\nvar listCmd = &cobra.Command{\n\tUse:   \"list\",\n\tShort: \"Lists built-in integrations\",\n\tLong:  ``,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tintegrationProvider := integration.NewIntegration()\n\t\tintegrations := integrationProvider.List()\n\n\t\tfmt.Println(color.YellowString(\"Active:\"))\n\t\tfor _, i := range integrations {\n\t\t\tb, err := integrationProvider.IsActivate(i)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif b {\n\t\t\t\tfmt.Printf(\"> %s\\n\", color.GreenString(i))\n\t\t\t}\n\t\t}\n\n\t\tfmt.Println(color.YellowString(\"Unused: \"))\n\t\tfor _, i := range integrations {\n\t\t\tb, err := integrationProvider.IsActivate(i)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tif !b {\n\t\t\t\tfmt.Printf(\"> %s\\n\", color.GreenString(i))\n\t\t\t}\n\t\t}\n\t},\n}\n\nfunc init() {\n\tIntegrationCmd.AddCommand(listCmd)\n\n}\n"
  },
  {
    "path": "cmd/root.go",
    "content": "/*\nCopyright 2023 The K8sGPT Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/adrg/xdg\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/analyze\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/auth\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/cache\"\n\tcustomanalyzer \"github.com/k8sgpt-ai/k8sgpt/cmd/customAnalyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/dump\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/filters\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/generate\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/integration\"\n\t\"github.com/k8sgpt-ai/k8sgpt/cmd/serve\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n)\n\nvar (\n\tcfgFile     string\n\tkubecontext string\n\tkubeconfig  string\n\tverbose     bool\n\tVersion     string\n\tCommit      string\n\tDate        string\n)\n\n// rootCmd represents the base command when called without any subcommands\nvar rootCmd = &cobra.Command{\n\tUse:   \"k8sgpt\",\n\tShort: \"Kubernetes debugging powered by AI\",\n\tLong:  ``,\n\t// Uncomment the following line if your bare application\n\t// has an action associated with it:\n\t// Run: func(cmd *cobra.Command, args []string) { },\n}\n\n// Execute adds all child commands to the root command and sets flags appropriately.\n// This is called by main.main(). It only needs to happen once to the rootCmd.\nfunc Execute(v string, c string, d string) {\n\tVersion = v\n\tCommit = c\n\tDate = d\n\tviper.Set(\"Version\", Version)\n\tviper.Set(\"Commit\", Commit)\n\tviper.Set(\"Date\", Date)\n\terr := rootCmd.Execute()\n\tif err != nil {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc init() {\n\tperformConfigMigrationIfNeeded()\n\n\tcobra.OnInitialize(initConfig)\n\n\trootCmd.AddCommand(auth.AuthCmd)\n\trootCmd.AddCommand(analyze.AnalyzeCmd)\n\trootCmd.AddCommand(dump.DumpCmd)\n\trootCmd.AddCommand(filters.FiltersCmd)\n\trootCmd.AddCommand(generate.GenerateCmd)\n\trootCmd.AddCommand(integration.IntegrationCmd)\n\trootCmd.AddCommand(serve.ServeCmd)\n\trootCmd.AddCommand(cache.CacheCmd)\n\trootCmd.AddCommand(customanalyzer.CustomAnalyzerCmd)\n\trootCmd.PersistentFlags().StringVar(&cfgFile, \"config\", \"\", fmt.Sprintf(\"Default config file (%s/k8sgpt/k8sgpt.yaml)\", xdg.ConfigHome))\n\trootCmd.PersistentFlags().StringVar(&kubecontext, \"kubecontext\", \"\", \"Kubernetes context to use. Only required if out-of-cluster.\")\n\trootCmd.PersistentFlags().StringVar(&kubeconfig, \"kubeconfig\", \"\", \"Path to a kubeconfig. Only required if out-of-cluster.\")\n\trootCmd.PersistentFlags().BoolVarP(&verbose, \"verbose\", \"v\", false, \"Show detailed tool actions (e.g., API calls, checks).\")\n}\n\n// initConfig reads in config file and ENV variables if set.\nfunc initConfig() {\n\tif cfgFile != \"\" {\n\t\t// Use config file from the flag.\n\t\tviper.SetConfigFile(cfgFile)\n\t} else {\n\t\t// the config will belocated under `~/.config/k8sgpt/k8sgpt.yaml` on linux\n\t\tconfigDir := filepath.Join(xdg.ConfigHome, \"k8sgpt\")\n\n\t\tviper.AddConfigPath(configDir)\n\t\tviper.SetConfigType(\"yaml\")\n\t\tviper.SetConfigName(\"k8sgpt\")\n\n\t\t_ = viper.SafeWriteConfig()\n\t}\n\n\tviper.Set(\"kubecontext\", kubecontext)\n\tviper.Set(\"kubeconfig\", kubeconfig)\n\tviper.Set(\"verbose\", verbose)\n\n\tviper.SetEnvPrefix(\"K8SGPT\")\n\tviper.AutomaticEnv() // read in environment variables that match\n\n\t// If a config file is found, read it in.\n\tif err := viper.ReadInConfig(); err == nil {\n\t\t_ = 1\n\t\t//\tfmt.Fprintln(os.Stderr, \"Using config file:\", viper.ConfigFileUsed())\n\t}\n}\n\nfunc performConfigMigrationIfNeeded() {\n\toldConfig, err := getLegacyConfigFilePath()\n\tcobra.CheckErr(err)\n\toldConfigExists, err := util.FileExists(oldConfig)\n\tcobra.CheckErr(err)\n\n\tnewConfig := getConfigFilePath()\n\tnewConfigExists, err := util.FileExists(newConfig)\n\tcobra.CheckErr(err)\n\n\tconfigDir := filepath.Dir(newConfig)\n\terr = util.EnsureDirExists(configDir)\n\tcobra.CheckErr(err)\n\n\tif oldConfigExists && !newConfigExists {\n\t\terr = os.Rename(oldConfig, newConfig)\n\t\tcobra.CheckErr(err)\n\t}\n}\n\nfunc getConfigFilePath() string {\n\treturn filepath.Join(xdg.ConfigHome, \"k8sgpt\", \"k8sgpt.yaml\")\n}\n\nfunc getLegacyConfigFilePath() (string, error) {\n\thome, err := os.UserHomeDir()\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn filepath.Join(home, \".k8sgpt.yaml\"), nil\n}\n"
  },
  {
    "path": "cmd/root_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n)\n\n// Test that verbose flag is correctly set in viper.\nfunc TestInitConfig_VerboseFlag(t *testing.T) {\n\tverbose = true\n\tviper.Reset()\n\tinitConfig()\n\tif !viper.GetBool(\"verbose\") {\n\t\tt.Error(\"Expected verbose flag to be true\")\n\t}\n}\n"
  },
  {
    "path": "cmd/serve/serve.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 serve\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\n\tk8sgptserver \"github.com/k8sgpt-ai/k8sgpt/pkg/server\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\tdefaultTemperature float32 = 0.7\n\tdefaultTopP        float32 = 1.0\n\tdefaultTopK        int32   = 50\n\tdefaultMaxTokens   int     = 2048\n)\n\nvar (\n\tport        string\n\tmetricsPort string\n\tbackend     string\n\tenableHttp  bool\n\tenableMCP   bool\n\tmcpPort     string\n\tmcpHTTP     bool\n\t// filters can be injected into the server (repeatable flag)\n\tfilters     []string\n)\n\nvar ServeCmd = &cobra.Command{\n\tUse:   \"serve\",\n\tShort: \"Runs k8sgpt as a server\",\n\tLong:  `Runs k8sgpt as a server to allow for easy integration with other applications.`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\n\t\tvar configAI ai.AIConfiguration\n\t\terr := viper.UnmarshalKey(\"ai\", &configAI)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tvar aiProvider *ai.AIProvider\n\t\tif len(configAI.Providers) == 0 {\n\t\t\t// we validate and set temperature for our backend\n\t\t\ttemperature := func() float32 {\n\t\t\t\tenv := os.Getenv(\"K8SGPT_TEMPERATURE\")\n\t\t\t\tif env == \"\" {\n\t\t\t\t\treturn defaultTemperature\n\t\t\t\t}\n\t\t\t\ttemperature, err := strconv.ParseFloat(env, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcolor.Red(\"Unable to convert Temperature value: %v\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\tif temperature > 1.0 || temperature < 0.0 {\n\t\t\t\t\tcolor.Red(\"Error: temperature ranges from 0 to 1.\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn float32(temperature)\n\t\t\t}\n\t\t\ttopP := func() float32 {\n\t\t\t\tenv := os.Getenv(\"K8SGPT_TOP_P\")\n\t\t\t\tif env == \"\" {\n\t\t\t\t\treturn defaultTopP\n\t\t\t\t}\n\t\t\t\ttopP, err := strconv.ParseFloat(env, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcolor.Red(\"Unable to convert topP value: %v\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\tif topP > 1.0 || topP < 0.0 {\n\t\t\t\t\tcolor.Red(\"Error: topP ranges from 0 to 1.\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn float32(topP)\n\t\t\t}\n\t\t\ttopK := func() int32 {\n\t\t\t\tenv := os.Getenv(\"K8SGPT_TOP_K\")\n\t\t\t\tif env == \"\" {\n\t\t\t\t\treturn defaultTopK\n\t\t\t\t}\n\t\t\t\ttopK, err := strconv.ParseFloat(env, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcolor.Red(\"Unable to convert topK value: %v\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\tif topK < 10 || topK > 100 {\n\t\t\t\t\tcolor.Red(\"Error: topK ranges from 1 to 100.\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn int32(topK)\n\t\t\t}\n\t\t\tmaxTokens := func() int {\n\t\t\t\tenv := os.Getenv(\"K8SGPT_MAX_TOKENS\")\n\t\t\t\tif env == \"\" {\n\t\t\t\t\treturn defaultMaxTokens\n\t\t\t\t}\n\t\t\t\tmaxTokens, err := strconv.ParseInt(env, 10, 32)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcolor.Red(\"Unable to convert maxTokens value: %v\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t\treturn int(maxTokens)\n\t\t\t}\n\t\t\t// Check for env injection\n\t\t\tbackend = os.Getenv(\"K8SGPT_BACKEND\")\n\t\t\tpassword := os.Getenv(\"K8SGPT_PASSWORD\")\n\t\t\tmodel := os.Getenv(\"K8SGPT_MODEL\")\n\t\t\tbaseURL := os.Getenv(\"K8SGPT_BASEURL\")\n\t\t\tengine := os.Getenv(\"K8SGPT_ENGINE\")\n\t\t\tproxyEndpoint := os.Getenv(\"K8SGPT_PROXY_ENDPOINT\")\n\t\t\tproviderId := os.Getenv(\"K8SGPT_PROVIDER_ID\")\n\t\t\t// If the envs are set, allocate in place to the aiProvider\n\t\t\t// else exit with error\n\t\t\tenvIsSet := backend != \"\" || password != \"\" || model != \"\"\n\t\t\tif envIsSet {\n\t\t\t\taiProvider = &ai.AIProvider{\n\t\t\t\t\tName:          backend,\n\t\t\t\t\tPassword:      password,\n\t\t\t\t\tModel:         model,\n\t\t\t\t\tBaseURL:       baseURL,\n\t\t\t\t\tEngine:        engine,\n\t\t\t\t\tProxyEndpoint: proxyEndpoint,\n\t\t\t\t\tProviderId:    providerId,\n\t\t\t\t\tTemperature:   temperature(),\n\t\t\t\t\tTopP:          topP(),\n\t\t\t\t\tTopK:          topK(),\n\t\t\t\t\tMaxTokens:     maxTokens(),\n\t\t\t\t}\n\n\t\t\t\tconfigAI.Providers = append(configAI.Providers, *aiProvider)\n\n\t\t\t\tviper.Set(\"ai\", configAI)\n\t\t\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\t\t\tcolor.Red(\"Error writing config file: %s\", err.Error())\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolor.Red(\"Error: AI provider not specified in configuration. Please run k8sgpt auth\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}\n\t\tif aiProvider == nil {\n\t\t\tfor _, provider := range configAI.Providers {\n\t\t\t\tif backend == provider.Name {\n\t\t\t\t\t// the pointer to the range variable is not really an issue here, as there\n\t\t\t\t\t// is a break right after, but to prevent potential future issues, a temp\n\t\t\t\t\t// variable is assigned\n\t\t\t\t\tp := provider\n\t\t\t\t\taiProvider = &p\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif aiProvider == nil || aiProvider.Name == \"\" {\n\t\t\tcolor.Red(\"Error: AI provider %s not specified in configuration. Please run k8sgpt auth\", backend)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tlogger, err := zap.NewProduction()\n\t\tif err != nil {\n\t\t\tcolor.Red(\"failed to create logger: %v\", err)\n\t\t\tos.Exit(1)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := logger.Sync(); err != nil {\n\t\t\t\tcolor.Red(\"failed to sync logger: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}()\n\n\t\tif enableMCP {\n\t\t\t// Create and start MCP server\n\t\t\tmcpServer, err := k8sgptserver.NewMCPServer(mcpPort, aiProvider, mcpHTTP, logger)\n\t\t\tif err != nil {\n\t\t\t\tcolor.Red(\"Error creating MCP server: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tgo func() {\n\t\t\t\tif err := mcpServer.Start(); err != nil {\n\t\t\t\t\tcolor.Red(\"Error starting MCP server: %v\", err)\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\t// Allow metrics port to be overridden by environment variable\n\t\tif envMetricsPort := os.Getenv(\"K8SGPT_METRICS_PORT\"); envMetricsPort != \"\" && !cmd.Flags().Changed(\"metrics-port\") {\n\t\t\tmetricsPort = envMetricsPort\n\t\t}\n\n\t\tserver := k8sgptserver.Config{\n\t\t\tBackend:     aiProvider.Name,\n\t\t\tPort:        port,\n\t\t\tMetricsPort: metricsPort,\n\t\t\tEnableHttp:  enableHttp,\n\t\t\tToken:       aiProvider.Password,\n\t\t\tLogger:      logger,\n\t\t\tFilters:     filters,\n\t\t}\n\t\tgo func() {\n\t\t\tif err := server.ServeMetrics(); err != nil {\n\t\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}()\n\n\t\tgo func() {\n\t\t\tif err := server.Serve(); err != nil {\n\t\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t}()\n\n\t\t// Wait for both servers to exit\n\t\tselect {}\n\t},\n}\n\nfunc init() {\n\t// add flag for backend\n\tServeCmd.Flags().StringVarP(&port, \"port\", \"p\", \"8080\", \"Port to run the server on\")\n\tServeCmd.Flags().StringVarP(&metricsPort, \"metrics-port\", \"m\", \"8081\", \"Port to run the metrics-server on (env: K8SGPT_METRICS_PORT)\")\n\tServeCmd.Flags().StringVarP(&backend, \"backend\", \"b\", \"openai\", \"Backend AI provider\")\n\tServeCmd.Flags().BoolVarP(&enableHttp, \"http\", \"\", false, \"Enable REST/http using gppc-gateway\")\n\tServeCmd.Flags().BoolVarP(&enableMCP, \"mcp\", \"\", false, \"Enable Mission Control Protocol server\")\n\tServeCmd.Flags().StringVarP(&mcpPort, \"mcp-port\", \"\", \"8089\", \"Port to run the MCP server on\")\n\tServeCmd.Flags().BoolVarP(&mcpHTTP, \"mcp-http\", \"\", false, \"Enable HTTP mode for MCP server\")\n\t// allow injecting filters into the running server (repeatable)\n\tServeCmd.Flags().StringSliceVar(&filters, \"filter\", []string{}, \"Filter to apply (can be specified multiple times)\")\n}\n"
  },
  {
    "path": "cmd/version.go",
    "content": "/*\nCopyright 2023 The K8sGPT Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage cmd\n\nimport (\n\t\"fmt\"\n\t\"runtime/debug\"\n\n\t\"github.com/spf13/cobra\"\n)\n\n// versionCmd represents the version command\nvar versionCmd = &cobra.Command{\n\tUse:   \"version\",\n\tShort: \"Print the version number of k8sgpt\",\n\tLong:  `All software has versions. This is k8sgpt's`,\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\tif Version == \"dev\" {\n\t\t\tdetails, ok := debug.ReadBuildInfo()\n\t\t\tif ok && details.Main.Version != \"\" && details.Main.Version != \"(devel)\" {\n\t\t\t\tVersion = details.Main.Version\n\t\t\t\tfor _, i := range details.Settings {\n\t\t\t\t\tif i.Key == \"vcs.time\" {\n\t\t\t\t\t\tDate = i.Value\n\t\t\t\t\t}\n\t\t\t\t\tif i.Key == \"vcs.revision\" {\n\t\t\t\t\t\tCommit = i.Value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfmt.Printf(\"k8sgpt: %s (%s), built at: %s\\n\", Version, Commit, Date)\n\t},\n}\n\nfunc init() {\n\trootCmd.AddCommand(versionCmd)\n}\n"
  },
  {
    "path": "container/Dockerfile",
    "content": "# Copyright 2023 The K8sGPT Authors.\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#     http://www.apache.org/licenses/LICENSE-2.0\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM golang:1.24-alpine3.23 AS builder\n\nENV CGO_ENABLED=0\nARG VERSION\nARG COMMIT\nARG DATE\nWORKDIR /workspace\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY ./ ./\n\nRUN go build -o /workspace/k8sgpt -ldflags \"-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}\" ./ \n\nFROM gcr.io/distroless/static AS production\n\nLABEL org.opencontainers.image.source=\"https://github.com/k8sgpt-ai/k8sgpt\" \\\n    org.opencontainers.image.url=\"https://k8sgpt.ai\" \\\n    org.opencontainers.image.title=\"k8sgpt\" \\\n    org.opencontainers.image.vendor='The K8sGPT Authors' \\\n    org.opencontainers.image.licenses='Apache-2.0'\n\nWORKDIR /\nCOPY --from=builder /workspace/k8sgpt .\nUSER 65532:65532\n\nENTRYPOINT [\"/k8sgpt\"]\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/k8sgpt-ai/k8sgpt\n\ngo 1.24.1\n\ntoolchain go1.24.11\n\nrequire (\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/kedacore/keda/v2 v2.16.0\n\tgithub.com/magiconair/properties v1.8.9\n\tgithub.com/mittwald/go-helm-client v0.12.14\n\tgithub.com/ollama/ollama v0.13.4\n\tgithub.com/sashabaranov/go-openai v1.36.0\n\tgithub.com/schollz/progressbar/v3 v3.17.1\n\tgithub.com/spf13/cobra v1.8.1\n\tgithub.com/spf13/viper v1.19.0\n\tgithub.com/stretchr/testify v1.10.0\n\tgolang.org/x/term v0.33.0\n\thelm.sh/helm/v3 v3.17.4\n\tk8s.io/api v0.32.3\n\tk8s.io/apimachinery v0.32.3\n\tk8s.io/client-go v0.32.3\n\tk8s.io/kubectl v0.32.2 // indirect\n\n)\n\nrequire github.com/adrg/xdg v0.5.3\n\nrequire (\n\tbuf.build/gen/go/interplex-ai/schemas/grpc/go v1.5.1-20241117203254-a91193b62179.1\n\tbuf.build/gen/go/interplex-ai/schemas/protocolbuffers/go v1.35.2-20241117203254-a91193b62179.1\n\tbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.24.0-20241118152629-1379a5a1889d.1\n\tbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.5.1-20241118152629-1379a5a1889d.1\n\tbuf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.35.2-20241118152629-1379a5a1889d.1\n\tcloud.google.com/go/storage v1.50.0\n\tcloud.google.com/go/vertexai v0.13.2\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1\n\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0\n\tgithub.com/IBM/watsonx-go v1.0.1\n\tgithub.com/agiledragon/gomonkey/v2 v2.13.0\n\tgithub.com/aws/aws-sdk-go v1.55.7\n\tgithub.com/aws/aws-sdk-go-v2 v1.36.3\n\tgithub.com/aws/aws-sdk-go-v2/config v1.29.14\n\tgithub.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0\n\tgithub.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0\n\tgithub.com/aws/smithy-go v1.22.2\n\tgithub.com/cohere-ai/cohere-go/v2 v2.12.2\n\tgithub.com/go-logr/zapr v1.3.0\n\tgithub.com/google/generative-ai-go v0.19.0\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3\n\tgithub.com/hupe1980/go-huggingface v0.0.15\n\tgithub.com/kyverno/policy-reporter-kyverno-plugin v1.6.4\n\tgithub.com/mark3labs/mcp-go v0.36.0\n\tgithub.com/olekukonko/tablewriter v0.0.5\n\tgithub.com/oracle/oci-go-sdk/v65 v65.79.0\n\tgithub.com/prometheus/prometheus v0.306.0\n\tgithub.com/pterm/pterm v0.12.80\n\tgoogle.golang.org/api v0.239.0\n\tgopkg.in/yaml.v2 v2.4.0\n\tsigs.k8s.io/controller-runtime v0.19.3\n\tsigs.k8s.io/gateway-api v1.2.1\n)\n\nrequire (\n\tatomicgo.dev/cursor v0.2.0 // indirect\n\tatomicgo.dev/keyboard v0.2.9 // indirect\n\tatomicgo.dev/schedule v0.1.0 // indirect\n\tcel.dev/expr v0.23.0 // indirect\n\tcloud.google.com/go v0.120.0 // indirect\n\tcloud.google.com/go/ai v0.8.0 // indirect\n\tcloud.google.com/go/aiplatform v1.85.0 // indirect\n\tcloud.google.com/go/auth v0.16.2 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.7.0 // indirect\n\tcloud.google.com/go/iam v1.5.2 // indirect\n\tcloud.google.com/go/longrunning v0.6.7 // indirect\n\tcloud.google.com/go/monitoring v1.24.2 // indirect\n\tdario.cat/mergo v1.0.1 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect\n\tgithub.com/Microsoft/hcsshim v0.12.4 // indirect\n\tgithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect\n\tgithub.com/bahlo/generic-list-go v0.2.0 // indirect\n\tgithub.com/blang/semver/v4 v4.0.0 // indirect\n\tgithub.com/buger/jsonparser v1.1.1 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect\n\tgithub.com/containerd/console v1.0.4 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/platforms v0.2.1 // indirect\n\tgithub.com/creack/pty v1.1.21 // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.9.0 // indirect\n\tgithub.com/expr-lang/expr v1.17.7 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.0.5 // indirect\n\tgithub.com/gofrs/flock v0.12.1 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.2.2 // indirect\n\tgithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.14.2 // indirect\n\tgithub.com/gookit/color v1.5.4 // indirect\n\tgithub.com/gorilla/websocket v1.5.1 // indirect\n\tgithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect\n\tgithub.com/invopop/jsonschema v0.13.0 // indirect\n\tgithub.com/jmespath/go-jmespath v0.4.0 // indirect\n\tgithub.com/jpillora/backoff v1.0.0 // indirect\n\tgithub.com/kylelemons/godebug v1.1.0 // indirect\n\tgithub.com/lithammer/fuzzysearch v1.1.8 // indirect\n\tgithub.com/moby/sys/mountinfo v0.7.1 // 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/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgithub.com/prometheus/sigv4 v0.2.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.6.0 // indirect\n\tgithub.com/sagikazarmark/slog-shim v0.1.0 // indirect\n\tgithub.com/segmentio/fasthash v1.0.3 // indirect\n\tgithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect\n\tgithub.com/sony/gobreaker v0.5.0 // indirect\n\tgithub.com/sourcegraph/conc v0.3.0 // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.5.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/wk8/go-ordered-map/v2 v2.1.8 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/yosida95/uritemplate/v3 v3.0.2 // indirect\n\tgithub.com/zeebo/errs v1.4.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.4.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tknative.dev/pkg v0.0.0-20241026180704-25f6002b00f3 // indirect\n)\n\nrequire (\n\tgithub.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect\n\tgithub.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // 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.3.0 // indirect\n\tgithub.com/Masterminds/sprig/v3 v3.3.0 // indirect\n\tgithub.com/Masterminds/squirrel v1.5.4 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/chai2010/gettext-go v1.0.3 // indirect\n\tgithub.com/containerd/containerd v1.7.29 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.3.6 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/docker/cli v26.1.4+incompatible // indirect\n\tgithub.com/docker/distribution v2.8.3+incompatible // indirect\n\tgithub.com/docker/docker v28.3.0+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.8.2 // indirect\n\tgithub.com/docker/go-connections v0.5.0 // indirect\n\tgithub.com/docker/go-metrics v0.0.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.12.1 // indirect\n\tgithub.com/evanphx/json-patch v5.9.0+incompatible // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect\n\tgithub.com/fsnotify/fsnotify v1.8.0 // indirect\n\tgithub.com/go-errors/errors v1.5.1 // indirect\n\tgithub.com/go-gorp/gorp/v3 v3.1.0 // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/google/btree v1.1.2 // indirect\n\tgithub.com/google/gnostic v0.7.0\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/gosuri/uitable v0.0.4 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/huandu/xstrings v1.5.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jmoiron/sqlx v1.4.0 // indirect\n\tgithub.com/josharian/intern 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/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect\n\tgithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect\n\tgithub.com/lib/pq v1.10.9 // 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/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moby/locker v1.0.1 // indirect\n\tgithub.com/moby/spdystream v0.5.0 // indirect\n\tgithub.com/moby/term v0.5.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.3 // indirect\n\tgithub.com/peterbourgon/diskv v2.0.1+incompatible // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_golang v1.23.0-rc.1\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/robfig/cron/v3 v3.0.1\n\tgithub.com/rubenv/sql-migrate v1.7.1 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/spf13/afero v1.11.0 // indirect\n\tgithub.com/spf13/cast v1.7.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgo.opentelemetry.io/otel v1.36.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.36.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0\n\tgolang.org/x/crypto v0.40.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect\n\tgolang.org/x/net v0.42.0\n\tgolang.org/x/oauth2 v0.30.0 // indirect\n\tgolang.org/x/sync v0.16.0 // indirect\n\tgolang.org/x/sys v0.36.0 // indirect\n\tgolang.org/x/text v0.27.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgoogle.golang.org/grpc v1.73.0\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tk8s.io/apiextensions-apiserver v0.32.2\n\tk8s.io/apiserver v0.32.2 // indirect\n\tk8s.io/cli-runtime v0.32.2 // indirect\n\tk8s.io/component-base v0.32.2 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect\n\tk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2\n\toras.land/oras-go v1.2.5 // indirect\n\tsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect\n\tsigs.k8s.io/kustomize/api v0.18.0 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.18.1 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n\n// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0\n// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible\n//replace oras.land/oras-go => oras.land/oras-go v1.2.4\nreplace github.com/docker/docker => github.com/docker/docker v28.0.4+incompatible\n\nreplace dario.cat/mergo => github.com/imdario/mergo v1.0.1\n"
  },
  {
    "path": "go.sum",
    "content": "atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=\natomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ=\natomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=\natomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=\natomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=\natomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=\natomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs=\natomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU=\nbuf.build/gen/go/interplex-ai/schemas/grpc/go v1.5.1-20241117203254-a91193b62179.1 h1:v8dSqv/mIjimtO6c1FwknMWW1D7BXK3VPToy/UHpqkY=\nbuf.build/gen/go/interplex-ai/schemas/grpc/go v1.5.1-20241117203254-a91193b62179.1/go.mod h1:+AL6UMLDgY2rP2ZgM3tlDBnemFklPzUX1rtR7MJKmM4=\nbuf.build/gen/go/interplex-ai/schemas/protocolbuffers/go v1.35.2-20241117203254-a91193b62179.1 h1:DTYmvB5P/Dc1oL3oS75pu8bkGcSGgATGhVxqB0uMkPw=\nbuf.build/gen/go/interplex-ai/schemas/protocolbuffers/go v1.35.2-20241117203254-a91193b62179.1/go.mod h1:UeiXHRr2O8y37OLckIQilBNcy22+u41dADFpEXeUH+I=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.24.0-20241118152629-1379a5a1889d.1 h1:9r9t2pVf+X8oesYpfTATH9FGIWbVy70eExkEQWj/qrA=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.24.0-20241118152629-1379a5a1889d.1/go.mod h1:M+KYheBX0z4c6yvFj2WUmr/Qs1KtxwsecD6Hv9SUo/s=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.5.1-20241118152629-1379a5a1889d.1 h1:thB3o5jG9fU30xDbC5+PC3DLyB8/TjGtObhvC/A+AK0=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.5.1-20241118152629-1379a5a1889d.1/go.mod h1:rlbkTkVN2P3aNR0U/7N5d9/uvNW8/dzHwtJDfPzh2vc=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.35.2-20241118152629-1379a5a1889d.1 h1:Z+fW0kWryP6LdjP+z+d1/WT4tObrq890aye4aPIh6hM=\nbuf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.35.2-20241118152629-1379a5a1889d.1/go.mod h1:dqopmdpTDT6p9kPTxVCgR8WDnNb1SjZjwzaNj/kRbps=\ncel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=\ncel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\ncloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\ncloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=\ncloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=\ncloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=\ncloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=\ncloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=\ncloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=\ncloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=\ncloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=\ncloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=\ncloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=\ncloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=\ncloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U=\ncloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=\ncloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=\ncloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=\ncloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=\ncloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=\ncloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=\ncloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=\ncloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=\ncloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=\ncloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=\ncloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=\ncloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=\ncloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=\ncloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=\ncloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM=\ncloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ=\ncloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=\ncloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=\ncloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=\ncloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=\ncloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=\ncloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ=\ncloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k=\ncloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw=\ncloud.google.com/go/aiplatform v1.85.0 h1:80/GqdP8Tovaaw9Qr6fYZNDvwJeA9rLk8mYkqBJNIJQ=\ncloud.google.com/go/aiplatform v1.85.0/go.mod h1:S4DIKz3TFLSt7ooF2aCRdAqsUR4v/YDXUoHqn5P0EFc=\ncloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=\ncloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=\ncloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M=\ncloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE=\ncloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE=\ncloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk=\ncloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=\ncloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8=\ncloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc=\ncloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=\ncloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8=\ncloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY=\ncloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM=\ncloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc=\ncloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU=\ncloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI=\ncloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8=\ncloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno=\ncloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=\ncloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84=\ncloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A=\ncloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E=\ncloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=\ncloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=\ncloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY=\ncloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k=\ncloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=\ncloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=\ncloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0=\ncloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=\ncloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI=\ncloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ=\ncloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI=\ncloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08=\ncloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=\ncloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=\ncloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=\ncloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ=\ncloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=\ncloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo=\ncloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg=\ncloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw=\ncloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=\ncloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=\ncloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=\ncloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo=\ncloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=\ncloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E=\ncloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=\ncloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=\ncloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=\ncloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8=\ncloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=\ncloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU=\ncloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc=\ncloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=\ncloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss=\ncloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE=\ncloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=\ncloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g=\ncloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4=\ncloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=\ncloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM=\ncloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\ncloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=\ncloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw=\ncloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=\ncloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E=\ncloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac=\ncloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q=\ncloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=\ncloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=\ncloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=\ncloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=\ncloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=\ncloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss=\ncloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc=\ncloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=\ncloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=\ncloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0=\ncloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=\ncloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q=\ncloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg=\ncloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=\ncloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8=\ncloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk=\ncloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=\ncloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE=\ncloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU=\ncloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U=\ncloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=\ncloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M=\ncloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg=\ncloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s=\ncloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM=\ncloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=\ncloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA=\ncloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=\ncloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=\ncloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4=\ncloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=\ncloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y=\ncloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs=\ncloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=\ncloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=\ncloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=\ncloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=\ncloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=\ncloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=\ncloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=\ncloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=\ncloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=\ncloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=\ncloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA=\ncloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=\ncloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=\ncloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=\ncloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=\ncloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=\ncloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=\ncloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=\ncloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=\ncloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=\ncloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=\ncloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=\ncloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg=\ncloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=\ncloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4=\ncloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM=\ncloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA=\ncloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=\ncloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=\ncloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI=\ncloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s=\ncloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=\ncloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=\ncloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=\ncloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE=\ncloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=\ncloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M=\ncloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0=\ncloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8=\ncloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=\ncloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=\ncloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE=\ncloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=\ncloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=\ncloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=\ncloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA=\ncloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE=\ncloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38=\ncloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=\ncloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8=\ncloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=\ncloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=\ncloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM=\ncloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA=\ncloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=\ncloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ=\ncloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs=\ncloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s=\ncloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=\ncloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4=\ncloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=\ncloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=\ncloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c=\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/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=\ncloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c=\ncloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=\ncloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=\ncloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g=\ncloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=\ncloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs=\ncloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww=\ncloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c=\ncloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=\ncloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI=\ncloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ=\ncloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=\ncloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=\ncloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=\ncloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek=\ncloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=\ncloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM=\ncloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4=\ncloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE=\ncloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM=\ncloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=\ncloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4=\ncloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=\ncloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=\ncloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k=\ncloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=\ncloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM=\ncloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs=\ncloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=\ncloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=\ncloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE=\ncloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=\ncloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=\ncloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc=\ncloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY=\ncloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=\ncloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI=\ncloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=\ncloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M=\ncloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc=\ncloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=\ncloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw=\ncloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY=\ncloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w=\ncloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=\ncloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs=\ncloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg=\ncloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=\ncloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=\ncloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=\ncloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY=\ncloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=\ncloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw=\ncloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA=\ncloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c=\ncloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=\ncloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=\ncloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w=\ncloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=\ncloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0=\ncloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60=\ncloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=\ncloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg=\ncloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=\ncloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=\ncloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw=\ncloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=\ncloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=\ncloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E=\ncloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw=\ncloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA=\ncloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=\ncloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y=\ncloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=\ncloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM=\ncloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=\ncloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo=\ncloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=\ncloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=\ncloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=\ncloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=\ncloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=\ncloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=\ncloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY=\ncloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY=\ncloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0=\ncloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=\ncloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=\ncloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc=\ncloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=\ncloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk=\ncloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo=\ncloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74=\ncloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM=\ncloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=\ncloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4=\ncloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs=\ncloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=\ncloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o=\ncloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE=\ncloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA=\ncloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg=\ncloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=\ncloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=\ncloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=\ncloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=\ncloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=\ncloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=\ncloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=\ncloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=\ncloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=\ncloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY=\ncloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=\ncloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=\ncloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo=\ncloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=\ncloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=\ncloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=\ncloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=\ncloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=\ncloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=\ncloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=\ncloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=\ncloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=\ncloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE=\ncloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=\ncloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA=\ncloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=\ncloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw=\ncloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY=\ncloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=\ncloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=\ncloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I=\ncloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=\ncloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=\ncloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA=\ncloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=\ncloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM=\ncloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=\ncloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=\ncloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8=\ncloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=\ncloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo=\ncloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk=\ncloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=\ncloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=\ncloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=\ncloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=\ncloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=\ncloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=\ncloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=\ncloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=\ncloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=\ncloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E=\ncloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM=\ncloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8=\ncloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=\ncloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY=\ncloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=\ncloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=\ncloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k=\ncloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU=\ncloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=\ncloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=\ncloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA=\ncloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=\ncloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE=\ncloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ=\ncloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4=\ncloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=\ncloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI=\ncloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA=\ncloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=\ncloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ=\ncloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE=\ncloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=\ncloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc=\ncloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=\ncloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=\ncloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo=\ncloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=\ncloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw=\ncloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=\ncloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=\ncloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70=\ncloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=\ncloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs=\ncloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=\ncloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=\ncloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk=\ncloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg=\ncloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=\ncloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw=\ncloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc=\ncloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=\ncloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=\ncloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg=\ncloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\ncloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI=\ncloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=\ncloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8=\ncloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4=\ncloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=\ncloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k=\ncloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM=\ncloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=\ncloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=\ncloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=\ncloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=\ncloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE=\ncloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=\ncloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA=\ncloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c=\ncloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=\ncloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=\ncloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac=\ncloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=\ncloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=\ncloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs=\ncloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=\ncloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ=\ncloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=\ncloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=\ncloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA=\ncloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=\ncloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ=\ncloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA=\ncloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=\ncloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots=\ncloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo=\ncloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI=\ncloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU=\ncloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=\ncloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA=\ncloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=\ncloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=\ncloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc=\ncloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=\ncloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14=\ncloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do=\ncloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=\ncloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM=\ncloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg=\ncloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=\ncloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=\ncloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk=\ncloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=\ncloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc=\ncloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc=\ncloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=\ncloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4=\ncloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=\ncloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU=\ncloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=\ncloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=\ncloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=\ncloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q=\ncloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=\ncloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8=\ncloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0=\ncloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=\ncloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=\ncloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk=\ncloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=\ncloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0=\ncloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag=\ncloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU=\ncloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=\ncloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA=\ncloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc=\ncloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk=\ncloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=\ncloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=\ncloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4=\ncloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=\ncloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY=\ncloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s=\ncloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco=\ncloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=\ncloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc=\ncloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4=\ncloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E=\ncloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=\ncloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec=\ncloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA=\ncloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4=\ncloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=\ncloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A=\ncloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=\ncloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=\ncloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=\ncloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=\ncloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=\ncloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=\ncloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=\ncloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0=\ncloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ncloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=\ncloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=\ncloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=\ncloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=\ncloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=\ncloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs=\ncloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY=\ncloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=\ncloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=\ncloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=\ncloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw=\ncloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=\ncloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=\ncloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM=\ncloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=\ncloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c=\ncloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8=\ncloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=\ncloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc=\ncloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ=\ncloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=\ncloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM=\ncloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28=\ncloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=\ncloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA=\ncloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk=\ncloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=\ncloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=\ncloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs=\ncloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=\ncloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0=\ncloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos=\ncloud.google.com/go/vertexai v0.13.2 h1:dOnvkMDZy3GdKAz8Isd2d6KV3jQpk6CKvYao1SIupuk=\ncloud.google.com/go/vertexai v0.13.2/go.mod h1:+nmz1z8AeYILA5QM2yii3CED1PqGknZH1CUNDVatIg4=\ncloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk=\ncloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=\ncloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg=\ncloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk=\ncloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ=\ncloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=\ncloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=\ncloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M=\ncloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=\ncloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU=\ncloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=\ncloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=\ncloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=\ncloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY=\ncloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=\ncloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY=\ncloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0=\ncloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE=\ncloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=\ncloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc=\ncloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY=\ncloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=\ncloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8=\ncloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY=\ncloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w=\ncloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=\ncloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes=\ncloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=\ncloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=\ncloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc=\ncloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=\ncloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg=\ncloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo=\ncloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=\ncloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng=\ncloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=\ncloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=\ncloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=\ncloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=\ncloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=\ngit.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=\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/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0/go.mod h1:QyiQdW4f4/BIfB8ZutZ2s+28RAgfa/pT+zS++ZHyM1I=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0 h1:bXwSugBiSbgtz7rOtbfGf+woewp4f06orW9OP5BjHLA=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4 v4.3.0/go.mod h1:Y/HgrePTmGy9HjdSGTqZNa+apUpTVIEVKXJyARP2lrk=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo=\ngithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=\ngithub.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=\ngithub.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=\ngithub.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=\ngithub.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=\ngithub.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0 h1:nNMpRpnkWDAaqcpxMJvxa/Ud98gjbYwayJY4/9bdjiU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.50.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=\ngithub.com/IBM/watsonx-go v1.0.1 h1:Juj90I8ZpJWR/Oq4ISJzQhMJwI6q+DAaZy+f/8W7KDA=\ngithub.com/IBM/watsonx-go v1.0.1/go.mod h1:8lzvpe/158JkrzvcoIcIj6OdNty5iC9co5nQHfkhRtM=\ngithub.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=\ngithub.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=\ngithub.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=\ngithub.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=\ngithub.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=\ngithub.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=\ngithub.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=\ngithub.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=\ngithub.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=\ngithub.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=\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.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=\ngithub.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=\ngithub.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=\ngithub.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=\ngithub.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/Rr4=\ngithub.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=\ngithub.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=\ngithub.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=\ngithub.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=\ngithub.com/agiledragon/gomonkey/v2 v2.13.0 h1:B24Jg6wBI1iB8EFR1c+/aoTg7QN/Cum7YffG8KMIyYo=\ngithub.com/agiledragon/gomonkey/v2 v2.13.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=\ngithub.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=\ngithub.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=\ngithub.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=\ngithub.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=\ngithub.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=\ngithub.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=\ngithub.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=\ngithub.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=\ngithub.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=\ngithub.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=\ngithub.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=\ngithub.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=\ngithub.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=\ngithub.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=\ngithub.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0 h1:2P70khV5KDzoRs8UuplU3rAzzyLaj5kzND33Jutwpbg=\ngithub.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0/go.mod h1:rZOgAxQVRg9v5ZEQHrrKw0Gkb9DBAASeeRiwUmmXcG0=\ngithub.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0 h1:eMOwQ8ZZK+76+08RfxeaGUtRFN6wxmD1rvqovc2kq2w=\ngithub.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0/go.mod h1:0b5Rq7rUvSQFYHI1UO0zFTV/S6j6DUyuykXA80C+YOI=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=\ngithub.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=\ngithub.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=\ngithub.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=\ngithub.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=\ngithub.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=\ngithub.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\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/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=\ngithub.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng=\ngithub.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=\ngithub.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=\ngithub.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=\ngithub.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=\ngithub.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=\ngithub.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=\ngithub.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=\ngithub.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=\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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=\ngithub.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=\ngithub.com/cohere-ai/cohere-go/v2 v2.12.2 h1:8WJqqcCe3q6TB1CdhgzJOgRO2ouno8xcYcOoeWtI8Pk=\ngithub.com/cohere-ai/cohere-go/v2 v2.12.2/go.mod h1:MuiJkCxlR18BDV2qQPbz2Yb/OCVphT1y6nD2zYaKeR0=\ngithub.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=\ngithub.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=\ngithub.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=\ngithub.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=\ngithub.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=\ngithub.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=\ngithub.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE=\ngithub.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs=\ngithub.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=\ngithub.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=\ngithub.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=\ngithub.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=\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/digitalocean/godo v1.157.0 h1:ReELaS6FxXNf8gryUiVH0wmyUmZN8/NCmBX4gXd3F0o=\ngithub.com/digitalocean/godo v1.157.0/go.mod h1:tYeiWY5ZXVpU48YaFv0M5irUFHXGorZpDNm7zzdWMzM=\ngithub.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=\ngithub.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=\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/docker/cli v26.1.4+incompatible h1:I8PHdc0MtxEADqYJZvhBrW9bo8gawKwwenxRM7/rLu8=\ngithub.com/docker/cli v26.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=\ngithub.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=\ngithub.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=\ngithub.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=\ngithub.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=\ngithub.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=\ngithub.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=\ngithub.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=\ngithub.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=\ngithub.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=\ngithub.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=\ngithub.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=\ngithub.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls=\ngithub.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=\ngithub.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=\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/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=\ngithub.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=\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/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=\ngithub.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=\ngithub.com/foxcpp/go-mockdns v1.1.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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=\ngithub.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=\ngithub.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=\ngithub.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=\ngithub.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY=\ngithub.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY=\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-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=\ngithub.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=\ngithub.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=\ngithub.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=\ngithub.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=\ngithub.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=\ngithub.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=\ngithub.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=\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/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=\ngithub.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=\ngithub.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=\ngithub.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=\ngithub.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\ngithub.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=\ngithub.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=\ngithub.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=\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.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=\ngithub.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=\ngithub.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=\ngithub.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=\ngithub.com/google/gnostic v0.7.0 h1:d7EpuFp8vVdML+y0JJJYiKeOLjKTdH/GvVkLOBWqJpw=\ngithub.com/google/gnostic v0.7.0/go.mod h1:IAcUyMl6vtC95f60EZ8oXyqTsOersP6HbwjeG7EyDPM=\ngithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=\ngithub.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\ngithub.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=\ngithub.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0/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/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=\ngithub.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=\ngithub.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=\ngithub.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=\ngithub.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=\ngithub.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=\ngithub.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=\ngithub.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=\ngithub.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=\ngithub.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=\ngithub.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=\ngithub.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=\ngithub.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\ngithub.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=\ngithub.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=\ngithub.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=\ngithub.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=\ngithub.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=\ngithub.com/gophercloud/gophercloud/v2 v2.7.0 h1:o0m4kgVcPgHlcXiWAjoVxGd8QCmvM5VU+YM71pFbn0E=\ngithub.com/gophercloud/gophercloud/v2 v2.7.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=\ngithub.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=\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.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=\ngithub.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=\ngithub.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=\ngithub.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=\ngithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=\ngithub.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=\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/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=\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/consul/api v1.32.0 h1:5wp5u780Gri7c4OedGEPzmlUEzi0g2KyiPphSr6zjVg=\ngithub.com/hashicorp/consul/api v1.32.0/go.mod h1:Z8YgY0eVPukT/17ejW+l+C7zJmKwgPHtjU1q16v/Y40=\ngithub.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A=\ngithub.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4=\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-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=\ngithub.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=\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/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=\ngithub.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=\ngithub.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=\ngithub.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=\ngithub.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=\ngithub.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec h1:+YBzb977VrmffaCX/OBm17dEVJUcWn5dW+eqs3aIJ/A=\ngithub.com/hashicorp/nomad/api v0.0.0-20241218080744-e3ac00f30eec/go.mod h1:svtxn6QnrQ69P23VvIWMR34tg3vmwLz4UdUzm1dSCgE=\ngithub.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=\ngithub.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=\ngithub.com/hetznercloud/hcloud-go/v2 v2.21.1 h1:IH3liW8/cCRjfJ4cyqYvw3s1ek+KWP8dl1roa0lD8JM=\ngithub.com/hetznercloud/hcloud-go/v2 v2.21.1/go.mod h1:XOaYycZJ3XKMVWzmqQ24/+1V7ormJHmPdck/kxrNnQA=\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/hupe1980/go-huggingface v0.0.15 h1:tTWmUGGunC/BYz4hrwS8SSVtMYVYjceG2uhL8HxeXvw=\ngithub.com/hupe1980/go-huggingface v0.0.15/go.mod h1:IRvsik3+b9BJyw9hCfw1arI6gDObcVto1UA8f3kt8mM=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/imdario/mergo v1.0.1 h1:lFIgOs30GMaV/2+qQ+eEBLbUL6h1YosdohE3ODy4hTs=\ngithub.com/imdario/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=\ngithub.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=\ngithub.com/ionos-cloud/sdk-go/v6 v6.3.4 h1:jTvGl4LOF8v8OYoEIBNVwbFoqSGAFqn6vGE7sp7/BqQ=\ngithub.com/ionos-cloud/sdk-go/v6 v6.3.4/go.mod h1:wCVwNJ/21W29FWFUv+fNawOTMlFoP1dS3L+ZuztFW48=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=\ngithub.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=\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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=\ngithub.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=\ngithub.com/kedacore/keda/v2 v2.16.0 h1:0ZoqAeGHORh0B/BOBLDf6fRVvgc5ATeuQCgEm6bNViM=\ngithub.com/kedacore/keda/v2 v2.16.0/go.mod h1:17Yth2jUQvi5KZGGIRmL4ZlwFZY/0FDxIG5jSa+IjpY=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=\ngithub.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=\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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=\ngithub.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=\ngithub.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=\ngithub.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=\ngithub.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=\ngithub.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=\ngithub.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/kyverno/policy-reporter-kyverno-plugin v1.6.4 h1:mJYDLS+uGhNos+qjkDJUfeJlLs90MsDANBOhWoQfnh4=\ngithub.com/kyverno/policy-reporter-kyverno-plugin v1.6.4/go.mod h1:NMA8PofO4aPrEpw6b74COjx5/WD2EUAXWN3bAMGGxhU=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\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/linode/linodego v1.52.2 h1:N9ozU27To1LMSrDd8WvJZ5STSz1eGYdyLnxhAR/dIZg=\ngithub.com/linode/linodego v1.52.2/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA=\ngithub.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=\ngithub.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=\ngithub.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=\ngithub.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=\ngithub.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=\ngithub.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=\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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=\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.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=\ngithub.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=\ngithub.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=\ngithub.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=\ngithub.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=\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-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/mittwald/go-helm-client v0.12.14 h1:az3GJ4kRmFK609Ic3iHXveNtg92n9jWG0YpKKTIK4oo=\ngithub.com/mittwald/go-helm-client v0.12.14/go.mod h1:2VogAupgnV7FiuoPqtpCYKS/RrMh9fFA3/pD/OmTaLc=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=\ngithub.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=\ngithub.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=\ngithub.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\ngithub.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=\ngithub.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=\ngithub.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=\ngithub.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=\ngithub.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=\ngithub.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=\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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/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-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/ollama/ollama v0.13.4 h1:COb7S3+mvXkAHG7vxqeD7uhAPJ/UCAn7OeGkiBBCo98=\ngithub.com/ollama/ollama v0.13.4/go.mod h1:2VxohsKICsmUCrBjowf+luTXYiXn2Q70Cnvv5Urbzkw=\ngithub.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=\ngithub.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=\ngithub.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=\ngithub.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=\ngithub.com/opencontainers/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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=\ngithub.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=\ngithub.com/oracle/oci-go-sdk/v65 v65.79.0 h1:Tv9L1XTKWkdXtSViMbP+dA93WunquvW++/2s5pOvOgU=\ngithub.com/oracle/oci-go-sdk/v65 v65.79.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=\ngithub.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=\ngithub.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=\ngithub.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=\ngithub.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=\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/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=\ngithub.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=\ngithub.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=\ngithub.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=\ngithub.com/prometheus/client_golang v1.23.0-rc.1 h1:Is/nGODd8OsJlNQSybeYBwY/B6aHrN7+QwVUYutHSgw=\ngithub.com/prometheus/client_golang v1.23.0-rc.1/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/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.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=\ngithub.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3 h1:R/zO7ombSHCI8bjQusgCMSL+cE669w5/R2upq5WlPD0=\ngithub.com/prometheus/common v0.65.1-0.20250703115700-7f8b2a0d32d3/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/prometheus/prometheus v0.306.0 h1:Q0Pvz/ZKS6vVWCa1VSgNyNJlEe8hxdRlKklFg7SRhNw=\ngithub.com/prometheus/prometheus v0.306.0/go.mod h1:7hMSGyZHt0dcmZ5r4kFPJ/vxPQU99N5/BGwSPDxeZrQ=\ngithub.com/prometheus/sigv4 v0.2.0 h1:qDFKnHYFswJxdzGeRP63c4HlH3Vbn1Yf/Ao2zabtVXk=\ngithub.com/prometheus/sigv4 v0.2.0/go.mod h1:D04rqmAaPPEUkjRQxGqjoxdyJuyCh6E0M18fZr0zBiE=\ngithub.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=\ngithub.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=\ngithub.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=\ngithub.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=\ngithub.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=\ngithub.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=\ngithub.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=\ngithub.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=\ngithub.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=\ngithub.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=\ngithub.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=\ngithub.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=\ngithub.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4=\ngithub.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4=\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/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=\ngithub.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=\ngithub.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=\ngithub.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=\ngithub.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=\ngithub.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=\ngithub.com/sashabaranov/go-openai v1.36.0 h1:fcSrn8uGuorzPWCBp8L0aCR95Zjb/Dd+ZSML0YZy9EI=\ngithub.com/sashabaranov/go-openai v1.36.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=\ngithub.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0=\ngithub.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk=\ngithub.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=\ngithub.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=\ngithub.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=\ngithub.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=\ngithub.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=\ngithub.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=\ngithub.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=\ngithub.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=\ngithub.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=\ngithub.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=\ngithub.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=\ngithub.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=\ngithub.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=\ngithub.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=\ngithub.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=\ngithub.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=\ngithub.com/stackitcloud/stackit-sdk-go/core v0.17.2 h1:jPyn+i8rkp2hM80+hOg0B/1EVRbMt778Tr5RWyK1m2E=\ngithub.com/stackitcloud/stackit-sdk-go/core v0.17.2/go.mod h1:8KIw3czdNJ9sdil9QQimxjR6vHjeINFrRv0iZ67wfn0=\ngithub.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=\ngithub.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=\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/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=\ngithub.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=\ngithub.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=\ngithub.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=\ngithub.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=\ngithub.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=\ngithub.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=\ngithub.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=\ngithub.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=\ngithub.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=\ngithub.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=\ngo.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=\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.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=\ngo.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=\ngo.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=\ngo.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=\ngo.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=\ngo.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=\ngo.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=\ngo.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=\ngo.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=\ngo.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=\ngo.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=\ngolang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=\ngolang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=\ngolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=\ngolang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=\ngolang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=\ngolang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=\ngolang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=\ngolang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=\ngolang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=\ngolang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-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-20190801041406-cbf593c0f2f3/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=\ngolang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=\ngolang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=\ngolang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.3.0/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-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\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-20190206041539-40960b6deb8e/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-20190328211700-ab21143f2384/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-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=\ngolang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngolang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=\ngomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=\ngomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=\ngonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=\ngonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=\ngonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=\ngonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=\ngonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=\ngonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=\ngonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\ngoogle.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\ngoogle.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=\ngoogle.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=\ngoogle.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=\ngoogle.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=\ngoogle.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=\ngoogle.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=\ngoogle.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=\ngoogle.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=\ngoogle.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=\ngoogle.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=\ngoogle.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=\ngoogle.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=\ngoogle.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=\ngoogle.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=\ngoogle.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=\ngoogle.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=\ngoogle.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=\ngoogle.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=\ngoogle.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=\ngoogle.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=\ngoogle.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=\ngoogle.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=\ngoogle.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=\ngoogle.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08=\ngoogle.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=\ngoogle.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=\ngoogle.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=\ngoogle.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=\ngoogle.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=\ngoogle.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=\ngoogle.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=\ngoogle.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=\ngoogle.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=\ngoogle.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=\ngoogle.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=\ngoogle.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=\ngoogle.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=\ngoogle.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=\ngoogle.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=\ngoogle.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=\ngoogle.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=\ngoogle.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=\ngoogle.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=\ngoogle.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=\ngoogle.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=\ngoogle.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=\ngoogle.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=\ngoogle.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=\ngoogle.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=\ngoogle.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=\ngoogle.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=\ngoogle.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=\ngoogle.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=\ngoogle.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=\ngoogle.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=\ngoogle.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=\ngoogle.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=\ngoogle.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=\ngoogle.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA=\ngoogle.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=\ngoogle.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA=\ngoogle.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=\ngoogle.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=\ngoogle.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=\ngoogle.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=\ngoogle.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=\ngoogle.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=\ngoogle.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=\ngoogle.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=\ngoogle.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=\ngoogle.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=\ngoogle.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=\ngoogle.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=\ngoogle.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=\ngoogle.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=\ngoogle.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=\ngoogle.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=\ngotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=\nhelm.sh/helm/v3 v3.17.4 h1:GK+vgn9gKCyoH44+f3B5zpA78iH3AK4ywIInDEmmn/g=\nhelm.sh/helm/v3 v3.17.4/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=\nk8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=\nk8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=\nk8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4=\nk8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA=\nk8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=\nk8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=\nk8s.io/apiserver v0.32.2 h1:WzyxAu4mvLkQxwD9hGa4ZfExo3yZZaYzoYvvVDlM6vw=\nk8s.io/apiserver v0.32.2/go.mod h1:PEwREHiHNU2oFdte7BjzA1ZyjWjuckORLIK/wLV5goM=\nk8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks=\nk8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8=\nk8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=\nk8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=\nk8s.io/component-base v0.32.2 h1:1aUL5Vdmu7qNo4ZsE+569PV5zFatM9hl+lb3dEea2zU=\nk8s.io/component-base v0.32.2/go.mod h1:PXJ61Vx9Lg+P5mS8TLd7bCIr+eMJRQTyXe8KvkrvJq0=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=\nk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=\nk8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=\nk8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=\nknative.dev/pkg v0.0.0-20241026180704-25f6002b00f3 h1:uUSDGlOIkdPT4svjlhi+JEnP2Ufw7AM/F5QDYiEL02U=\nknative.dev/pkg v0.0.0-20241026180704-25f6002b00f3/go.mod h1:FeMbTLlxQqSASwlRCrYEOsZ0OKUgSj52qxhECwYCJsw=\nlukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nlukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=\nmodernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=\nmodernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=\nmodernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=\nmodernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=\nmodernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=\nmodernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=\nmodernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=\nmodernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=\nmodernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=\nmodernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=\nmodernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=\nmodernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=\nmodernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=\nmodernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=\nmodernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=\nmodernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=\nmodernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=\nmodernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=\nmodernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=\nmodernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=\nmodernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=\nmodernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=\nmodernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=\nmodernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=\noras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=\noras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=\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/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw=\nsigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM=\nsigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM=\nsigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=\nsigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo=\nsigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U=\nsigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E=\nsigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "main.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 main\n\nimport \"github.com/k8sgpt-ai/k8sgpt/cmd\"\n\nvar (\n\tversion = \"dev\"\n\tcommit  = \"HEAD\"\n\tdate    = \"unknown\"\n)\n\nfunc main() {\n\tcmd.Execute(version, commit, date)\n}\n"
  },
  {
    "path": "pkg/ai/amazonbedrock.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawsconfig \"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrock\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrockruntime\"\n\t\"github.com/aws/smithy-go/middleware\"\n\tsmithyhttp \"github.com/aws/smithy-go/transport/http\"\n)\n\nconst amazonbedrockAIClientName = \"amazonbedrock\"\n\n// AmazonBedRockClient represents the client for interacting with the Amazon Bedrock service.\ntype AmazonBedRockClient struct {\n\tnopCloser\n\n\tclient      BedrockRuntimeAPI\n\tmgmtClient  BedrockManagementAPI\n\tmodel       *bedrock_support.BedrockModel\n\ttemperature float32\n\ttopP        float32\n\tmaxTokens   int\n\tmodels      []bedrock_support.BedrockModel\n}\n\n// AmazonCompletion BedRock support region list US East (N. Virginia),US West (Oregon),Asia Pacific (Singapore),Asia Pacific (Tokyo),Europe (Frankfurt)\n// https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html#bedrock-regions\nconst BEDROCK_DEFAULT_REGION = \"us-east-1\" // default use us-east-1 region\n\nconst (\n\tUS_East_1      = \"us-east-1\"\n\tUS_West_2      = \"us-west-2\"\n\tAP_Southeast_1 = \"ap-southeast-1\"\n\tAP_Northeast_1 = \"ap-northeast-1\"\n\tEU_Central_1   = \"eu-central-1\"\n\tAP_South_1     = \"ap-south-1\"\n\tUS_Gov_West_1  = \"us-gov-west-1\"\n\tUS_Gov_East_1  = \"us-gov-east-1\"\n)\n\nvar BEDROCKER_SUPPORTED_REGION = []string{\n\tUS_East_1,\n\tUS_West_2,\n\tAP_Southeast_1,\n\tAP_Northeast_1,\n\tEU_Central_1,\n\tAP_South_1,\n\tUS_Gov_West_1,\n\tUS_Gov_East_1,\n}\n\nvar defaultModels = []bedrock_support.BedrockModel{\n\n\t{\n\t\tName:       \"anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"us.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"us.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"eu.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"eu.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"apac.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"apac.anthropic.claude-sonnet-4-20250514-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"us.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"us.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"eu.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"eu.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"apac.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"apac.anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"us.anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"us.anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-v2\",\n\t\tCompletion: &bedrock_support.CohereCompletion{},\n\t\tResponse:   &bedrock_support.CohereResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-v2\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-v1\",\n\t\tCompletion: &bedrock_support.CohereCompletion{},\n\t\tResponse:   &bedrock_support.CohereResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-v1\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-instant-v1\",\n\t\tCompletion: &bedrock_support.CohereCompletion{},\n\t\tResponse:   &bedrock_support.CohereResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-instant-v1\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"ai21.j2-ultra-v1\",\n\t\tCompletion: &bedrock_support.AI21{},\n\t\tResponse:   &bedrock_support.AI21Response{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"ai21.j2-ultra-v1\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"ai21.j2-jumbo-instruct\",\n\t\tCompletion: &bedrock_support.AI21{},\n\t\tResponse:   &bedrock_support.AI21Response{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"ai21.j2-jumbo-instruct\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"amazon.titan-text-express-v1\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.AmazonResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"amazon.titan-text-express-v1\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"amazon.nova-pro-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\t// https://docs.aws.amazon.com/nova/latest/userguide/getting-started-api.html\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"amazon.nova-pro-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"eu.amazon.nova-pro-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\t// https://docs.aws.amazon.com/nova/latest/userguide/getting-started-api.html\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"eu.amazon.nova-pro-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"us.amazon.nova-pro-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\t// https://docs.aws.amazon.com/nova/latest/userguide/getting-started-api.html\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"us.amazon.nova-pro-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"amazon.nova-lite-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"amazon.nova-lite-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"eu.amazon.nova-lite-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"eu.amazon.nova-lite-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"us.amazon.nova-lite-v1:0\",\n\t\tCompletion: &bedrock_support.AmazonCompletion{},\n\t\tResponse:   &bedrock_support.NovaResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100, // max of 300k tokens\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"us.amazon.nova-lite-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-3-haiku-20240307-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t// sensible defaults\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-3-haiku-20240307-v1:0\",\n\t\t},\n\t},\n}\n\n// NewAmazonBedRockClient creates a new AmazonBedRockClient with the given models\nfunc NewAmazonBedRockClient(models []bedrock_support.BedrockModel) *AmazonBedRockClient {\n\tif models == nil {\n\t\tmodels = defaultModels // Use default models if none provided\n\t}\n\treturn &AmazonBedRockClient{\n\t\tmodels: models,\n\t}\n}\n\n// GetModelOrDefault check config region\nfunc GetRegionOrDefault(region string) string {\n\tif os.Getenv(\"AWS_DEFAULT_REGION\") != \"\" {\n\t\tregion = os.Getenv(\"AWS_DEFAULT_REGION\")\n\t}\n\t// Check if the provided model is in the list\n\tfor _, m := range BEDROCKER_SUPPORTED_REGION {\n\t\tif m == region {\n\t\t\treturn region // Return the provided model\n\t\t}\n\t}\n\n\t// Return the default model if the provided model is not in the list\n\treturn BEDROCK_DEFAULT_REGION\n}\n\nfunc validateModelArn(model string) bool {\n\tvar re = regexp.MustCompile(`(?m)^arn:(?P<Partition>[^:\\n]*):bedrock:(?P<Region>[^:\\n]*):(?P<AccountID>[^:\\n]*):(?P<Ignore>(?P<ResourceType>[^:\\/\\n]*)[:\\/])?(?P<Resource>.*)$`)\n\treturn re.MatchString(model)\n}\n\nfunc validateInferenceProfileArn(inferenceProfile string) bool {\n\t// Support both inference-profile and application-inference-profile formats\n\tvar re = regexp.MustCompile(`(?m)^arn:(?P<Partition>[^:\\n]*):bedrock:(?P<Region>[^:\\n]*):(?P<AccountID>[^:\\n]*):(?:inference-profile|application-inference-profile)\\/(?P<ProfileName>.+)$`)\n\treturn re.MatchString(inferenceProfile)\n}\n\n// Get model from string\nfunc (a *AmazonBedRockClient) getModelFromString(model string) (*bedrock_support.BedrockModel, error) {\n\tif model == \"\" {\n\t\treturn nil, errors.New(\"model name cannot be empty\")\n\t}\n\n\t// Trim spaces from the model name\n\tmodel = strings.TrimSpace(model)\n\n\t// Try to find an exact match first\n\tfor i := range a.models {\n\t\tif strings.EqualFold(model, a.models[i].Name) || strings.EqualFold(model, a.models[i].Config.ModelName) {\n\t\t\t// Create a copy to avoid returning a pointer to a loop variable\n\t\t\tmodelCopy := a.models[i]\n\t\t\treturn &modelCopy, nil\n\t\t}\n\t}\n\n\tsupportedModels := make([]string, len(a.models))\n\tfor i, m := range a.models {\n\t\tsupportedModels[i] = m.Name\n\t}\n\n\tsupportedRegions := BEDROCKER_SUPPORTED_REGION\n\n\t// Pretty-print supported models and regions\n\tmodelList := \"\"\n\tfor _, m := range supportedModels {\n\t\tmodelList += \"  - \" + m + \"\\n\"\n\t}\n\tregionList := \"\"\n\tfor _, r := range supportedRegions {\n\t\tregionList += \"  - \" + r + \"\\n\"\n\t}\n\n\treturn nil, fmt.Errorf(\n\t\t\"model '%s' not found in supported models.\\n\\nSupported models:\\n%sSupported regions:\\n%s\",\n\t\tmodel, modelList, regionList,\n\t)\n}\n\n// Configure configures the AmazonBedRockClient with the provided configuration.\nfunc (a *AmazonBedRockClient) Configure(config IAIConfig) error {\n\t// Initialize models if not already initialized\n\tif a.models == nil {\n\t\ta.models = defaultModels\n\t}\n\n\t// Get the model input\n\tmodelInput := config.GetModel()\n\n\t// Determine the appropriate region to use\n\tvar region string\n\n\t// Check if the model input is actually an inference profile ARN\n\tif validateInferenceProfileArn(modelInput) {\n\t\t// Extract the region from the inference profile ARN\n\t\tarnParts := strings.Split(modelInput, \":\")\n\t\tif len(arnParts) >= 4 {\n\t\t\tregion = arnParts[3]\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"could not extract region from inference profile ARN: %s\", modelInput)\n\t\t}\n\t} else {\n\t\t// Use the provided region or default\n\t\tregion = GetRegionOrDefault(config.GetProviderRegion())\n\t}\n\n\t// Only create AWS clients if they haven't been injected (for testing)\n\tif a.client == nil || a.mgmtClient == nil {\n\t\t// Create a new AWS config with the determined region\n\t\tcfg, err := awsconfig.LoadDefaultConfig(context.Background(),\n\t\t\tawsconfig.WithRegion(region),\n\t\t)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"InvalidAccessKeyId\") || strings.Contains(err.Error(), \"SignatureDoesNotMatch\") || strings.Contains(err.Error(), \"NoCredentialProviders\") {\n\t\t\t\treturn fmt.Errorf(\"AWS credentials are invalid or missing. Please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config. Details: %v\", err)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to load AWS config for region %s: %w\", region, err)\n\t\t}\n\n\t\t// Create clients with the config\n\t\ta.client = bedrockruntime.NewFromConfig(cfg)\n\t\ta.mgmtClient = bedrock.NewFromConfig(cfg)\n\t}\n\n\t// Handle model selection based on input type\n\tif validateInferenceProfileArn(modelInput) {\n\t\t// Get the inference profile details\n\t\tprofile, err := a.getInferenceProfile(context.Background(), modelInput)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to get inference profile: %v\", err)\n\t\t}\n\t\t// Extract the model ID from the inference profile\n\t\tmodelID, err := a.extractModelFromInferenceProfile(profile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to extract model ID from inference profile: %v\", err)\n\t\t}\n\t\t// Find the model configuration for the extracted model ID\n\t\tfoundModel, err := a.getModelFromString(modelID)\n\t\tif err != nil {\n\t\t\t// Instead of failing, use a generic config for completion/response\n\t\t\t// But still warn user\n\t\t\treturn fmt.Errorf(\"failed to find model configuration for %s: %v\", modelID, err)\n\t\t}\n\t\t// Use the found model config for completion/response, but set ModelName to the profile ARN\n\t\ta.model = foundModel\n\t\ta.model.Config.ModelName = modelInput\n\t\t// Mark that we're using an inference profile\n\t\t// (could add a field if needed)\n\t} else {\n\t\t// Regular model ID provided\n\t\tfoundModel, err := a.getModelFromString(modelInput)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"model '%s' is not supported: %v\", modelInput, err)\n\t\t}\n\t\ta.model = foundModel\n\t\ta.model.Config.ModelName = foundModel.Config.ModelName\n\t}\n\n\t// Set common configuration parameters\n\ta.temperature = config.GetTemperature()\n\ta.topP = config.GetTopP()\n\ta.maxTokens = config.GetMaxTokens()\n\n\treturn nil\n}\n\n// getInferenceProfile retrieves the inference profile details from Amazon Bedrock\nfunc (a *AmazonBedRockClient) getInferenceProfile(ctx context.Context, inferenceProfileARN string) (*bedrock.GetInferenceProfileOutput, error) {\n\t// Extract the profile ID from the ARN\n\t// ARN format: arn:aws:bedrock:region:account-id:inference-profile/profile-id\n\t// or arn:aws:bedrock:region:account-id:application-inference-profile/profile-id\n\tparts := strings.Split(inferenceProfileARN, \"/\")\n\tif len(parts) != 2 {\n\t\treturn nil, fmt.Errorf(\"invalid inference profile ARN format: %s\", inferenceProfileARN)\n\t}\n\n\tprofileID := parts[1]\n\n\t// Create the input for the GetInferenceProfile API call\n\tinput := &bedrock.GetInferenceProfileInput{\n\t\tInferenceProfileIdentifier: aws.String(profileID),\n\t}\n\n\t// Call the GetInferenceProfile API\n\toutput, err := a.mgmtClient.GetInferenceProfile(ctx, input)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get inference profile: %w\", err)\n\t}\n\n\treturn output, nil\n}\n\n// extractModelFromInferenceProfile extracts the model ID from the inference profile\nfunc (a *AmazonBedRockClient) extractModelFromInferenceProfile(profile *bedrock.GetInferenceProfileOutput) (string, error) {\n\tif profile == nil || len(profile.Models) == 0 {\n\t\treturn \"\", fmt.Errorf(\"inference profile does not contain any models\")\n\t}\n\n\t// Check if the first model has a non-nil ModelArn\n\tif profile.Models[0].ModelArn == nil {\n\t\treturn \"\", fmt.Errorf(\"model information is missing in inference profile\")\n\t}\n\n\t// Get the first model ARN from the profile\n\tmodelARN := aws.ToString(profile.Models[0].ModelArn)\n\tif modelARN == \"\" {\n\t\treturn \"\", fmt.Errorf(\"model ARN is empty in inference profile\")\n\t}\n\n\t// Extract the model ID from the ARN\n\t// ARN format: arn:aws:bedrock:region::foundation-model/model-id\n\tparts := strings.Split(modelARN, \"/\")\n\tif len(parts) != 2 {\n\t\treturn \"\", fmt.Errorf(\"invalid model ARN format: %s\", modelARN)\n\t}\n\n\tmodelID := parts[1]\n\treturn modelID, nil\n}\n\n// GetCompletion sends a request to the model for generating completion based on the provided prompt.\nfunc (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\t// override config defaults\n\ta.model.Config.MaxTokens = a.maxTokens\n\ta.model.Config.Temperature = a.temperature\n\ta.model.Config.TopP = a.topP\n\n\tsupportedModels := make([]string, len(a.models))\n\tfor i, m := range a.models {\n\t\tsupportedModels[i] = m.Name\n\t}\n\n\t// Allow valid inference profile ARNs as supported models\n\tif !bedrock_support.IsModelSupported(a.model.Config.ModelName, supportedModels) && !validateInferenceProfileArn(a.model.Config.ModelName) {\n\t\treturn \"\", fmt.Errorf(\"model '%s' is not supported.\\nSupported models:\\n%s\", a.model.Config.ModelName, func() string {\n\t\t\ts := \"\"\n\t\t\tfor _, m := range supportedModels {\n\t\t\t\ts += \"  - \" + m + \"\\n\"\n\t\t\t}\n\t\t\treturn s\n\t\t}())\n\t}\n\n\tbody, err := a.model.Completion.GetCompletion(ctx, prompt, a.model.Config)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Build the parameters for the model invocation\n\tparams := &bedrockruntime.InvokeModelInput{\n\t\tBody:        body,\n\t\tModelId:     aws.String(a.model.Config.ModelName),\n\t\tContentType: aws.String(\"application/json\"),\n\t\tAccept:      aws.String(\"application/json\"),\n\t}\n\n\t// Detect if the model name is an inference profile ARN and set the header if so\n\tvar optFns []func(*bedrockruntime.Options)\n\tif validateInferenceProfileArn(a.model.Config.ModelName) {\n\t\tinferenceProfileArn := a.model.Config.ModelName\n\t\toptFns = append(optFns, func(options *bedrockruntime.Options) {\n\t\t\toptions.APIOptions = append(options.APIOptions, func(stack *middleware.Stack) error {\n\t\t\t\treturn stack.Initialize.Add(middleware.InitializeMiddlewareFunc(\"InferenceProfileHeader\", func(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (out middleware.InitializeOutput, metadata middleware.Metadata, err error) {\n\t\t\t\t\treq, ok := in.Parameters.(*smithyhttp.Request)\n\t\t\t\t\tif ok {\n\t\t\t\t\t\treq.Header.Set(\"X-Amzn-Bedrock-Inference-Profile-ARN\", inferenceProfileArn)\n\t\t\t\t\t}\n\t\t\t\t\treturn next.HandleInitialize(ctx, in)\n\t\t\t\t}), middleware.Before)\n\t\t\t})\n\t\t})\n\t}\n\n\t// Invoke the model\n\tvar resp *bedrockruntime.InvokeModelOutput\n\tif len(optFns) > 0 {\n\t\tresp, err = a.client.InvokeModel(ctx, params, optFns...)\n\t} else {\n\t\tresp, err = a.client.InvokeModel(ctx, params)\n\t}\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"InvalidAccessKeyId\") || strings.Contains(err.Error(), \"SignatureDoesNotMatch\") || strings.Contains(err.Error(), \"NoCredentialProviders\") {\n\t\t\treturn \"\", fmt.Errorf(\"AWS credentials are invalid or missing. Please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config. Details: %v\", err)\n\t\t}\n\t\treturn \"\", err\n\t}\n\n\t// Parse the response\n\treturn a.model.Response.ParseResponse(resp.Body)\n}\n\n// GetName returns the name of the AmazonBedRockClient.\nfunc (a *AmazonBedRockClient) GetName() string {\n\treturn amazonbedrockAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/amazonbedrock_mock_test.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrock\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrock/types\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrockruntime\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n)\n\n// Mock for Bedrock Management Client\ntype MockBedrockClient struct {\n\tmock.Mock\n}\n\nfunc (m *MockBedrockClient) GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error) {\n\targs := m.Called(ctx, params)\n\t\n\tif args.Get(0) == nil {\n\t\treturn nil, args.Error(1)\n\t}\n\t\n\treturn args.Get(0).(*bedrock.GetInferenceProfileOutput), args.Error(1)\n}\n\n// Mock for Bedrock Runtime Client\ntype MockBedrockRuntimeClient struct {\n\tmock.Mock\n}\n\nfunc (m *MockBedrockRuntimeClient) InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error) {\n\targs := m.Called(ctx, params)\n\t\n\tif args.Get(0) == nil {\n\t\treturn nil, args.Error(1)\n\t}\n\t\n\treturn args.Get(0).(*bedrockruntime.InvokeModelOutput), args.Error(1)\n}\n\n// TestBedrockInferenceProfileARNWithMocks tests the inference profile ARN validation with mocks\nfunc TestBedrockInferenceProfileARNWithMocks(t *testing.T) {\n\t// Create test models\n\ttestModels := []bedrock_support.BedrockModel{\n\t\t{\n\t\t\tName:       \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t\tMaxTokens:   100,\n\t\t\t\tTemperature: 0.5,\n\t\t\t\tTopP:        0.9,\n\t\t\t\tModelName:   \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\t},\n\t\t},\n\t}\n\t\n\t// Create a client with test models\n\tclient := &AmazonBedRockClient{models: testModels}\n\t\n\t// Create mock clients\n\tmockMgmtClient := new(MockBedrockClient)\n\tmockRuntimeClient := new(MockBedrockRuntimeClient)\n\t\n\t// Inject mock clients into the AmazonBedRockClient\n\tclient.mgmtClient = mockMgmtClient\n\tclient.client = mockRuntimeClient\n\t\n\t// Test with a valid inference profile ARN\n\tinferenceProfileARN := \"arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile\"\n\t\n\t// Setup mock response for GetInferenceProfile\n\tmockMgmtClient.On(\"GetInferenceProfile\", mock.Anything, &bedrock.GetInferenceProfileInput{\n\t\tInferenceProfileIdentifier: aws.String(\"my-profile\"),\n\t}).Return(&bedrock.GetInferenceProfileOutput{\n\t\tModels: []types.InferenceProfileModel{\n\t\t\t{\n\t\t\t\tModelArn: aws.String(\"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0\"),\n\t\t\t},\n\t\t},\n\t}, nil)\n\t\n\t// Configure the client with the inference profile ARN\n\tconfig := AIProvider{\n\t\tModel:          inferenceProfileARN,\n\t\tProviderRegion: \"us-east-1\",\n\t}\n\t\n\t// Test the Configure method with the inference profile ARN\n\terr := client.Configure(&config)\n\t\n\t// Verify that the configuration was successful\n\tassert.NoError(t, err)\n\tassert.Equal(t, inferenceProfileARN, client.model.Config.ModelName)\n\t\n\t// Verify that the mock was called\n\tmockMgmtClient.AssertExpectations(t)\n}\n"
  },
  {
    "path": "pkg/ai/amazonbedrock_test.go",
    "content": "package ai\n\nimport (\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Test models for unit testing\nvar testModels = []bedrock_support.BedrockModel{\n\t{\n\t\tName:       \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\tCompletion: &bedrock_support.CohereCompletion{},\n\t\tResponse:   &bedrock_support.CohereResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\t},\n\t},\n\t{\n\t\tName:       \"anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\tCompletion: &bedrock_support.CohereCompletion{},\n\t\tResponse:   &bedrock_support.CohereResponse{},\n\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\tMaxTokens:   100,\n\t\t\tTemperature: 0.5,\n\t\t\tTopP:        0.9,\n\t\t\tModelName:   \"anthropic.claude-3-7-sonnet-20250219-v1:0\",\n\t\t},\n\t},\n}\n\nfunc TestBedrockModelConfig(t *testing.T) {\n\tclient := &AmazonBedRockClient{models: testModels}\n\n\t// Should return error for ARN input (no exact match)\n\t_, err := client.getModelFromString(\"arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0\")\n\tassert.NotNil(t, err, \"Should return error for ARN input\")\n}\n\nfunc TestBedrockInvalidModel(t *testing.T) {\n\tclient := &AmazonBedRockClient{models: testModels}\n\n\t// Should return error for invalid model name\n\t_, err := client.getModelFromString(\"arn:aws:s3:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0\")\n\tassert.NotNil(t, err, \"Should return error for invalid model name\")\n}\n\nfunc TestBedrockInferenceProfileARN(t *testing.T) {\n\t// Create a mock client with test models\n\tclient := &AmazonBedRockClient{models: testModels}\n\n\t// Test with a valid inference profile ARN\n\tinferenceProfileARN := \"arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile\"\n\tconfig := AIProvider{\n\t\tModel:          inferenceProfileARN,\n\t\tProviderRegion: \"us-east-1\",\n\t}\n\n\t// This will fail in a real environment without mocks, but we're just testing the validation logic\n\terr := client.Configure(&config)\n\t// We expect an error since we can't actually call AWS in tests\n\tassert.NotNil(t, err, \"Error should not be nil without AWS mocks\")\n\n\t// Test with a valid application inference profile ARN\n\tappInferenceProfileARN := \"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile\"\n\tconfig = AIProvider{\n\t\tModel:          appInferenceProfileARN,\n\t\tProviderRegion: \"us-east-1\",\n\t}\n\n\t// This will fail in a real environment without mocks, but we're just testing the validation logic\n\terr = client.Configure(&config)\n\t// We expect an error since we can't actually call AWS in tests\n\tassert.NotNil(t, err, \"Error should not be nil without AWS mocks\")\n\n\t// Test with an invalid inference profile ARN format\n\tinvalidARN := \"arn:aws:bedrock:us-east-1:123456789012:invalid-resource/my-profile\"\n\tconfig = AIProvider{\n\t\tModel:          invalidARN,\n\t\tProviderRegion: \"us-east-1\",\n\t}\n\n\terr = client.Configure(&config)\n\tassert.NotNil(t, err, \"Error should not be nil for invalid inference profile ARN format\")\n}\n\nfunc TestBedrockGetCompletionInferenceProfile(t *testing.T) {\n\tmodelName := \"arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0\"\n\tvar inferenceModelModels = []bedrock_support.BedrockModel{\n\t\t{\n\t\t\tName:       \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\tCompletion: &bedrock_support.CohereMessagesCompletion{},\n\t\t\tResponse:   &bedrock_support.CohereMessagesResponse{},\n\t\t\tConfig: bedrock_support.BedrockModelConfig{\n\t\t\t\tMaxTokens:   100,\n\t\t\t\tTemperature: 0.5,\n\t\t\t\tTopP:        0.9,\n\t\t\t\tModelName:   modelName,\n\t\t\t},\n\t\t},\n\t}\n\tclient := &AmazonBedRockClient{models: inferenceModelModels}\n\n\tconfig := AIProvider{\n\t\tModel: modelName,\n\t}\n\terr := client.Configure(&config)\n\tassert.Nil(t, err, \"Error should be nil\")\n\tassert.Equal(t, modelName, client.model.Config.ModelName, \"Model name should match\")\n}\n\nfunc TestGetModelFromString(t *testing.T) {\n\tclient := &AmazonBedRockClient{models: testModels}\n\n\ttests := []struct {\n\t\tname      string\n\t\tmodel     string\n\t\twantModel string\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname:      \"exact model name match\",\n\t\t\tmodel:     \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\twantModel: \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\twantErr:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"partial model name match\",\n\t\t\tmodel:     \"claude-3-5-sonnet\",\n\t\t\twantModel: \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"model name with different version\",\n\t\t\tmodel:     \"anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\t\twantModel: \"anthropic.claude-3-5-sonnet-20241022-v2:0\",\n\t\t\twantErr:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"non-existent model\",\n\t\t\tmodel:     \"non-existent-model\",\n\t\t\twantModel: \"\",\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"empty model name\",\n\t\t\tmodel:     \"\",\n\t\t\twantModel: \"\",\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"model name with extra spaces\",\n\t\t\tmodel:     \"  anthropic.claude-3-5-sonnet-20240620-v1:0  \",\n\t\t\twantModel: \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\twantErr:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"case insensitive match\",\n\t\t\tmodel:     \"ANTHROPIC.CLAUDE-3-5-SONNET-20240620-V1:0\",\n\t\t\twantModel: \"anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t\t\twantErr:   false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotModel, err := client.getModelFromString(tt.model)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"getModelFromString() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !tt.wantErr && gotModel.Name != tt.wantModel {\n\t\t\t\tt.Errorf(\"getModelFromString() = %v, want %v\", gotModel.Name, tt.wantModel)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestDefaultModels tests that the client works with default models\nfunc TestDefaultModels(t *testing.T) {\n\tclient := &AmazonBedRockClient{}\n\n\t// Configure should initialize default models\n\terr := client.Configure(&AIProvider{\n\t\tModel: \"anthropic.claude-v2\",\n\t})\n\n\tassert.NoError(t, err, \"Configure should not return an error\")\n\tassert.NotNil(t, client.models, \"Models should be initialized\")\n\tassert.NotEmpty(t, client.models, \"Models should not be empty\")\n\n\t// Test finding a default model\n\tmodel, err := client.getModelFromString(\"anthropic.claude-v2\")\n\tassert.NoError(t, err, \"Should find the model\")\n\tassert.Equal(t, \"anthropic.claude-v2\", model.Name, \"Should find the correct model\")\n}\n\nfunc TestValidateInferenceProfileArn(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tarn   string\n\t\tvalid bool\n\t}{\n\t\t{\n\t\t\tname:  \"valid inference profile ARN\",\n\t\t\tarn:   \"arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile\",\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"valid application inference profile ARN\",\n\t\t\tarn:   \"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile\",\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid service in ARN\",\n\t\t\tarn:   \"arn:aws:s3:us-east-1:123456789012:inference-profile/my-profile\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid resource type in ARN\",\n\t\t\tarn:   \"arn:aws:bedrock:us-east-1:123456789012:model/my-profile\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"malformed ARN\",\n\t\t\tarn:   \"arn:aws:bedrock:us-east-1:inference-profile/my-profile\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"not an ARN\",\n\t\t\tarn:   \"not-an-arn\",\n\t\t\tvalid: false,\n\t\t},\n\t\t{\n\t\t\tname:  \"empty string\",\n\t\t\tarn:   \"\",\n\t\t\tvalid: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := validateInferenceProfileArn(tt.arn)\n\t\t\tassert.Equal(t, tt.valid, result, \"validateInferenceProfileArn() result should match expected\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/ai/amazonsagemaker.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/sagemakerruntime\"\n)\n\nconst amazonsagemakerAIClientName = \"amazonsagemaker\"\n\ntype SageMakerAIClient struct {\n\tnopCloser\n\n\tclient      *sagemakerruntime.SageMakerRuntime\n\tmodel       string\n\ttemperature float32\n\tendpoint    string\n\ttopP        float32\n\ttopK        int32\n\tmaxTokens   int\n}\n\ntype Generations []struct {\n\tGeneration struct {\n\t\tRole    string `json:\"role\"`\n\t\tContent string `json:\"content\"`\n\t} `json:\"generation\"`\n}\n\ntype Request struct {\n\tInputs     [][]Message `json:\"inputs\"`\n\tParameters Parameters  `json:\"parameters\"`\n}\n\ntype Message struct {\n\tRole    string `json:\"role\"`\n\tContent string `json:\"content\"`\n}\n\ntype Parameters struct {\n\tMaxNewTokens int     `json:\"max_new_tokens\"`\n\tTopP         float64 `json:\"top_p\"`\n\tTopK         float64 `json:\"top_k\"`\n\tTemperature  float64 `json:\"temperature\"`\n}\n\nfunc (c *SageMakerAIClient) Configure(config IAIConfig) error {\n\n\t// Create a new AWS session\n\tsess := session.Must(session.NewSessionWithOptions(session.Options{\n\t\tConfig:            aws.Config{Region: aws.String(config.GetProviderRegion())},\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t}))\n\n\t// Create a new SageMaker runtime client\n\tc.client = sagemakerruntime.New(sess)\n\tc.model = config.GetModel()\n\tc.endpoint = config.GetEndpointName()\n\tc.temperature = config.GetTemperature()\n\tc.maxTokens = config.GetMaxTokens()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\treturn nil\n}\n\nfunc (c *SageMakerAIClient) GetCompletion(_ context.Context, prompt string) (string, error) {\n\t// Create a completion request\n\trequest := Request{\n\t\tInputs: [][]Message{\n\t\t\t{\n\t\t\t\t{Role: \"system\", Content: \"DEFAULT_PROMPT\"},\n\t\t\t\t{Role: \"user\", Content: prompt},\n\t\t\t},\n\t\t},\n\n\t\tParameters: Parameters{\n\t\t\tMaxNewTokens: int(c.maxTokens),\n\t\t\tTopP:         float64(c.topP),\n\t\t\tTopK:         float64(c.topK),\n\t\t\tTemperature:  float64(c.temperature),\n\t\t},\n\t}\n\n\t// Convert request to []byte\n\tbytesData, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Create an input object\n\tinput := &sagemakerruntime.InvokeEndpointInput{\n\t\tBody:             bytesData,\n\t\tEndpointName:     aws.String(c.endpoint),\n\t\tContentType:      aws.String(\"application/json\"), // Set the content type as per your model's requirements\n\t\tAccept:           aws.String(\"application/json\"), // Set the accept type as per your model's requirements\n\t\tCustomAttributes: aws.String(\"accept_eula=true\"),\n\t}\n\n\t// Call the InvokeEndpoint function\n\tresult, err := c.client.InvokeEndpoint(input)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// // Define a slice of Generations\n\tvar generations Generations\n\n\terr = json.Unmarshal([]byte(string(result.Body)), &generations)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// Check for length of generations\n\tif len(generations) != 1 {\n\t\treturn \"\", fmt.Errorf(\"Expected exactly one generation, but got %d\", len(generations))\n\t}\n\n\t// Access the content\n\tcontent := generations[0].Generation.Content\n\treturn content, nil\n}\n\nfunc (c *SageMakerAIClient) GetName() string {\n\treturn amazonsagemakerAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/azureopenai.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nconst azureAIClientName = \"azureopenai\"\n\ntype AzureAIClient struct {\n\tnopCloser\n\n\tclient      *openai.Client\n\tmodel       string\n\ttemperature float32\n\t// organizationId string\n}\n\nfunc (c *AzureAIClient) Configure(config IAIConfig) error {\n\ttoken := config.GetPassword()\n\tbaseURL := config.GetBaseURL()\n\tengine := config.GetEngine()\n\tproxyEndpoint := config.GetProxyEndpoint()\n\tdefaultConfig := openai.DefaultAzureConfig(token, baseURL)\n\torgId := config.GetOrganizationId()\n\n\tdefaultConfig.AzureModelMapperFunc = func(model string) string {\n\t\t// If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function\n\t\tazureModelMapping := map[string]string{\n\t\t\tmodel: engine,\n\t\t}\n\t\treturn azureModelMapping[model]\n\n\t}\n\n\tif proxyEndpoint != \"\" {\n\t\tproxyUrl, err := url.Parse(proxyEndpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttransport := &http.Transport{\n\t\t\tProxy: http.ProxyURL(proxyUrl),\n\t\t}\n\n\t\tdefaultConfig.HTTPClient = &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\t}\n\tif orgId != \"\" {\n\t\tdefaultConfig.OrgID = orgId\n\t}\n\n\tclient := openai.NewClientWithConfig(defaultConfig)\n\tif client == nil {\n\t\treturn errors.New(\"error creating Azure OpenAI client\")\n\t}\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\treturn nil\n}\n\nfunc (c *AzureAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\t// Create a completion request\n\tresp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{\n\t\tModel: c.model,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\tContent: prompt,\n\t\t\t},\n\t\t},\n\t\tTemperature: c.temperature,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resp.Choices[0].Message.Content, nil\n}\n\nfunc (c *AzureAIClient) GetName() string {\n\treturn azureAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_interfaces.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrock\"\n\t\"github.com/aws/aws-sdk-go-v2/service/bedrockruntime\"\n)\n\n// BedrockManagementAPI defines the interface for Bedrock management operations\ntype BedrockManagementAPI interface {\n\tGetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error)\n}\n\n// BedrockRuntimeAPI defines the interface for Bedrock runtime operations\ntype BedrockRuntimeAPI interface {\n\tInvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error)\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/completions.go",
    "content": "package bedrock_support\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype ICompletion interface {\n\tGetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error)\n}\n\ntype CohereCompletion struct {\n\tcompletion ICompletion\n}\n\nfunc (a *CohereCompletion) GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\trequest := map[string]interface{}{\n\t\t\"prompt\":               fmt.Sprintf(\"\\n\\nHuman: %s  \\n\\nAssistant:\", prompt),\n\t\t\"max_tokens_to_sample\": modelConfig.MaxTokens,\n\t\t\"temperature\":          modelConfig.Temperature,\n\t\t\"top_p\":                modelConfig.TopP,\n\t}\n\tbody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n\ntype CohereMessagesCompletion struct {\n\tcompletion ICompletion\n}\n\nfunc (a *CohereMessagesCompletion) GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\trequest := map[string]interface{}{\n\t\t\"max_tokens\":        modelConfig.MaxTokens,\n\t\t\"temperature\":       modelConfig.Temperature,\n\t\t\"top_p\":             modelConfig.TopP,\n\t\t\"anthropic_version\": \"bedrock-2023-05-31\", // Or another valid version\n\t\t\"messages\": []map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"role\":    \"user\",\n\t\t\t\t\"content\": prompt,\n\t\t\t},\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n\ntype AI21 struct {\n\tcompletion ICompletion\n}\n\nfunc (a *AI21) GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\trequest := map[string]interface{}{\n\t\t\"prompt\":      prompt,\n\t\t\"maxTokens\":   modelConfig.MaxTokens,\n\t\t\"temperature\": modelConfig.Temperature,\n\t\t\"topP\":        modelConfig.TopP,\n\t}\n\tbody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n\ntype AmazonCompletion struct {\n\tcompletion ICompletion\n}\n\n// Accepts a list of supported model names\nfunc IsModelSupported(modelName string, supportedModels []string) bool {\n\tfor _, supportedModel := range supportedModels {\n\t\tif strings.EqualFold(modelName, supportedModel) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Note: The caller should check model support before calling GetCompletion.\nfunc (a *AmazonCompletion) GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\tif a == nil || modelConfig.ModelName == \"\" {\n\t\treturn nil, fmt.Errorf(\"no model name provided to Bedrock completion\")\n\t}\n\tif strings.Contains(modelConfig.ModelName, \"nova\") {\n\t\treturn a.GetNovaCompletion(ctx, prompt, modelConfig)\n\t} else {\n\t\treturn a.GetDefaultCompletion(ctx, prompt, modelConfig)\n\t}\n}\n\nfunc (a *AmazonCompletion) GetDefaultCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\trequest := map[string]interface{}{\n\t\t\"inputText\": fmt.Sprintf(\"\\n\\nUser: %s\", prompt),\n\t\t\"textGenerationConfig\": map[string]interface{}{\n\t\t\t\"maxTokenCount\": modelConfig.MaxTokens,\n\t\t\t\"temperature\":   modelConfig.Temperature,\n\t\t\t\"topP\":          modelConfig.TopP,\n\t\t},\n\t}\n\tbody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n\nfunc (a *AmazonCompletion) GetNovaCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {\n\trequest := map[string]interface{}{\n\t\t\"inferenceConfig\": map[string]interface{}{\n\t\t\t\"max_new_tokens\": modelConfig.MaxTokens,\n\t\t\t\"temperature\":    modelConfig.Temperature,\n\t\t\t\"topP\":           modelConfig.TopP,\n\t\t},\n\t\t\"messages\": []map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"role\": \"user\",\n\t\t\t\t\"content\": []map[string]interface{}{\n\t\t\t\t\t{\n\t\t\t\t\t\t\"text\": prompt,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tbody, err := json.Marshal(request)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\treturn body, nil\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/completions_test.go",
    "content": "package bedrock_support\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCohereCompletion_GetCompletion(t *testing.T) {\n\tcompletion := &CohereCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   100,\n\t\tTemperature: 0.7,\n\t\tTopP:        0.9,\n\t}\n\tprompt := \"Test prompt\"\n\n\tbody, err := completion.GetCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"\\n\\nHuman: Test prompt  \\n\\nAssistant:\", request[\"prompt\"])\n\tassert.Equal(t, 100, int(request[\"max_tokens_to_sample\"].(float64)))\n\tassert.Equal(t, 0.7, request[\"temperature\"])\n\tassert.Equal(t, 0.9, request[\"top_p\"])\n}\n\nfunc TestAI21_GetCompletion(t *testing.T) {\n\tcompletion := &AI21{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   150,\n\t\tTemperature: 0.6,\n\t\tTopP:        0.8,\n\t}\n\tprompt := \"Another test prompt\"\n\n\tbody, err := completion.GetCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"Another test prompt\", request[\"prompt\"])\n\tassert.Equal(t, 150, int(request[\"maxTokens\"].(float64)))\n\tassert.Equal(t, 0.6, request[\"temperature\"])\n\tassert.Equal(t, 0.8, request[\"topP\"])\n}\n\nfunc TestAmazonCompletion_GetDefaultCompletion(t *testing.T) {\n\tcompletion := &AmazonCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   200,\n\t\tTemperature: 0.5,\n\t\tTopP:        0.7,\n\t\tModelName:   \"amazon.titan-text-express-v1\",\n\t}\n\tprompt := \"Default test prompt\"\n\n\tbody, err := completion.GetDefaultCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"\\n\\nUser: Default test prompt\", request[\"inputText\"])\n\ttextConfig := request[\"textGenerationConfig\"].(map[string]interface{})\n\tassert.Equal(t, 200, int(textConfig[\"maxTokenCount\"].(float64)))\n\tassert.Equal(t, 0.5, textConfig[\"temperature\"])\n\tassert.Equal(t, 0.7, textConfig[\"topP\"])\n}\n\nfunc TestAmazonCompletion_GetNovaCompletion(t *testing.T) {\n\tcompletion := &AmazonCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   250,\n\t\tTemperature: 0.4,\n\t\tTopP:        0.6,\n\t\tModelName:   \"amazon.nova-pro-v1:0\",\n\t}\n\tprompt := \"Nova test prompt\"\n\n\tbody, err := completion.GetNovaCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tinferenceConfig := request[\"inferenceConfig\"].(map[string]interface{})\n\tassert.Equal(t, 250, int(inferenceConfig[\"max_new_tokens\"].(float64)))\n\tassert.Equal(t, 0.4, inferenceConfig[\"temperature\"])\n\tassert.Equal(t, 0.6, inferenceConfig[\"topP\"])\n\n\tmessages := request[\"messages\"].([]interface{})\n\tmessage := messages[0].(map[string]interface{})\n\tcontent := message[\"content\"].([]interface{})\n\tcontentMap := content[0].(map[string]interface{})\n\tassert.Equal(t, \"Nova test prompt\", contentMap[\"text\"])\n}\n\nfunc TestAmazonCompletion_GetCompletion_Nova(t *testing.T) {\n\tcompletion := &AmazonCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   250,\n\t\tTemperature: 0.4,\n\t\tTopP:        0.6,\n\t\tModelName:   \"amazon.nova-pro-v1:0\",\n\t}\n\tprompt := \"Nova test prompt\"\n\n\tbody, err := completion.GetCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tinferenceConfig := request[\"inferenceConfig\"].(map[string]interface{})\n\tassert.Equal(t, 250, int(inferenceConfig[\"max_new_tokens\"].(float64)))\n\tassert.Equal(t, 0.4, inferenceConfig[\"temperature\"])\n\tassert.Equal(t, 0.6, inferenceConfig[\"topP\"])\n\n\tmessages := request[\"messages\"].([]interface{})\n\tmessage := messages[0].(map[string]interface{})\n\tcontent := message[\"content\"].([]interface{})\n\tcontentMap := content[0].(map[string]interface{})\n\tassert.Equal(t, \"Nova test prompt\", contentMap[\"text\"])\n}\n\nfunc TestAmazonCompletion_GetCompletion_Default(t *testing.T) {\n\tcompletion := &AmazonCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   200,\n\t\tTemperature: 0.5,\n\t\tTopP:        0.7,\n\t\tModelName:   \"amazon.titan-text-express-v1\",\n\t}\n\tprompt := \"Default test prompt\"\n\n\tbody, err := completion.GetCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n\n\tvar request map[string]interface{}\n\terr = json.Unmarshal(body, &request)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, \"\\n\\nUser: Default test prompt\", request[\"inputText\"])\n\ttextConfig := request[\"textGenerationConfig\"].(map[string]interface{})\n\tassert.Equal(t, 200, int(textConfig[\"maxTokenCount\"].(float64)))\n\tassert.Equal(t, 0.5, textConfig[\"temperature\"])\n\tassert.Equal(t, 0.7, textConfig[\"topP\"])\n}\n\nfunc TestAmazonCompletion_GetCompletion_Inference_Profile(t *testing.T) {\n\tcompletion := &AmazonCompletion{}\n\tmodelConfig := BedrockModelConfig{\n\t\tMaxTokens:   200,\n\t\tTemperature: 0.5,\n\t\tTopP:        0.7,\n\t\tModelName:   \"arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0\",\n\t}\n\tprompt := \"Test prompt\"\n\n\t_, err := completion.GetCompletion(context.Background(), prompt, modelConfig)\n\tassert.NoError(t, err)\n}\n\nfunc TestIsModelSupported(t *testing.T) {\n\tsupported := []string{\n\t\t\"anthropic.claude-v2\",\n\t\t\"anthropic.claude-v1\",\n\t}\n\tassert.True(t, IsModelSupported(\"anthropic.claude-v2\", supported))\n\tassert.False(t, IsModelSupported(\"unsupported-model\", supported))\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/model.go",
    "content": "package bedrock_support\n\ntype BedrockModelConfig struct {\n\tMaxTokens   int\n\tTemperature float32\n\tTopP        float32\n\tModelName   string\n}\ntype BedrockModel struct {\n\tName       string\n\tCompletion ICompletion\n\tResponse   IResponse\n\tConfig     BedrockModelConfig\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/model_test.go",
    "content": "package bedrock_support\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBedrockModelConfig(t *testing.T) {\n\tconfig := BedrockModelConfig{\n\t\tMaxTokens:   100,\n\t\tTemperature: 0.7,\n\t\tTopP:        0.9,\n\t\tModelName:   \"test-model\",\n\t}\n\n\tassert.Equal(t, 100, config.MaxTokens)\n\tassert.Equal(t, float32(0.7), config.Temperature)\n\tassert.Equal(t, float32(0.9), config.TopP)\n\tassert.Equal(t, \"test-model\", config.ModelName)\n}\n\nfunc TestBedrockModel(t *testing.T) {\n\tcompletion := &MockCompletion{}\n\tresponse := &MockResponse{}\n\tconfig := BedrockModelConfig{\n\t\tMaxTokens:   100,\n\t\tTemperature: 0.7,\n\t\tTopP:        0.9,\n\t\tModelName:   \"test-model\",\n\t}\n\n\tmodel := BedrockModel{\n\t\tName:       \"Test Model\",\n\t\tCompletion: completion,\n\t\tResponse:   response,\n\t\tConfig:     config,\n\t}\n\n\tassert.Equal(t, \"Test Model\", model.Name)\n\tassert.Equal(t, completion, model.Completion)\n\tassert.Equal(t, response, model.Response)\n\tassert.Equal(t, config, model.Config)\n}\n\n// MockCompletion is a mock implementation of the ICompletion interface\ntype MockCompletion struct{}\n\nfunc (m *MockCompletion) GetCompletion(ctx context.Context, prompt string, config BedrockModelConfig) ([]byte, error) {\n\treturn []byte(`{\"prompt\": \"mock prompt\"}`), nil\n}\n\n// MockResponse is a mock implementation of the IResponse interface\ntype MockResponse struct{}\n\nfunc (m *MockResponse) ParseResponse(body []byte) (string, error) {\n\treturn \"mock response\", nil\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/responses.go",
    "content": "package bedrock_support\n\nimport (\n\t\"encoding/json\"\n)\n\ntype IResponse interface {\n\tParseResponse(rawResponse []byte) (string, error)\n}\n\ntype CohereMessagesResponse struct {\n\tresponse IResponse\n}\n\nfunc (a *CohereMessagesResponse) ParseResponse(rawResponse []byte) (string, error) {\n\ttype InvokeModelResponseBody struct {\n\t\tID      string `json:\"id\"`\n\t\tType    string `json:\"type\"`\n\t\tRole    string `json:\"role\"`\n\t\tModel   string `json:\"model\"`\n\t\tContent []struct {\n\t\t\tType string `json:\"type\"`\n\t\t\tText string `json:\"text\"`\n\t\t} `json:\"content\"`\n\t\tStopReason   string      `json:\"stop_reason\"`\n\t\tStopSequence interface{} `json:\"stop_sequence\"` // Could be null\n\t\tUsage        struct {\n\t\t\tInputTokens  int `json:\"input_tokens\"`\n\t\t\tOutputTokens int `json:\"output_tokens\"`\n\t\t} `json:\"usage\"`\n\t}\n\n\toutput := &InvokeModelResponseBody{}\n\terr := json.Unmarshal(rawResponse, output)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Extract the text content from the Content array\n\tvar resultText string\n\tfor _, content := range output.Content {\n\t\tif content.Type == \"text\" {\n\t\t\tresultText += content.Text\n\t\t}\n\t}\n\n\treturn resultText, nil\n}\n\ntype CohereResponse struct {\n\tresponse IResponse\n}\n\nfunc (a *CohereResponse) ParseResponse(rawResponse []byte) (string, error) {\n\ttype InvokeModelResponseBody struct {\n\t\tCompletion  string `json:\"completion\"`\n\t\tStop_reason string `json:\"stop_reason\"`\n\t}\n\toutput := &InvokeModelResponseBody{}\n\terr := json.Unmarshal(rawResponse, output)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn output.Completion, nil\n}\n\ntype AI21Response struct {\n\tresponse IResponse\n}\n\nfunc (a *AI21Response) ParseResponse(rawResponse []byte) (string, error) {\n\ttype Data struct {\n\t\tText string `json:\"text\"`\n\t}\n\ttype Completion struct {\n\t\tData Data `json:\"data\"`\n\t}\n\ttype InvokeModelResponseBody struct {\n\t\tCompletions []Completion `json:\"completions\"`\n\t}\n\toutput := &InvokeModelResponseBody{}\n\terr := json.Unmarshal(rawResponse, output)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn output.Completions[0].Data.Text, nil\n}\n\ntype AmazonResponse struct {\n\tresponse IResponse\n}\n\ntype NovaResponse struct {\n\tresponse NResponse\n}\ntype NResponse interface {\n\tParseResponse(rawResponse []byte) (string, error)\n}\n\nfunc (a *AmazonResponse) ParseResponse(rawResponse []byte) (string, error) {\n\ttype Result struct {\n\t\tTokenCount       int    `json:\"tokenCount\"`\n\t\tOutputText       string `json:\"outputText\"`\n\t\tCompletionReason string `json:\"completionReason\"`\n\t}\n\ttype InvokeModelResponseBody struct {\n\t\tInputTextTokenCount int      `json:\"inputTextTokenCount\"`\n\t\tResults             []Result `json:\"results\"`\n\t}\n\toutput := &InvokeModelResponseBody{}\n\terr := json.Unmarshal(rawResponse, output)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn output.Results[0].OutputText, nil\n}\n\nfunc (a *NovaResponse) ParseResponse(rawResponse []byte) (string, error) {\n\ttype Content struct {\n\t\tText string `json:\"text\"`\n\t}\n\n\ttype Message struct {\n\t\tRole    string    `json:\"role\"`\n\t\tContent []Content `json:\"content\"`\n\t}\n\n\ttype UsageDetails struct {\n\t\tInputTokens               int `json:\"inputTokens\"`\n\t\tOutputTokens              int `json:\"outputTokens\"`\n\t\tTotalTokens               int `json:\"totalTokens\"`\n\t\tCacheReadInputTokenCount  int `json:\"cacheReadInputTokenCount\"`\n\t\tCacheWriteInputTokenCount int `json:\"cacheWriteInputTokenCount,omitempty\"`\n\t}\n\n\ttype AmazonNovaResponse struct {\n\t\tOutput struct {\n\t\t\tMessage Message `json:\"message\"`\n\t\t} `json:\"output\"`\n\t\tStopReason string       `json:\"stopReason\"`\n\t\tUsage      UsageDetails `json:\"usage\"`\n\t}\n\n\tresponse := &AmazonNovaResponse{}\n\terr := json.Unmarshal(rawResponse, response)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(response.Output.Message.Content) > 0 {\n\t\treturn response.Output.Message.Content[0].Text, nil\n\t}\n\n\treturn \"\", nil\n}\n"
  },
  {
    "path": "pkg/ai/bedrock_support/responses_test.go",
    "content": "package bedrock_support\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCohereResponse_ParseResponse(t *testing.T) {\n\tresponse := &CohereResponse{}\n\trawResponse := []byte(`{\"completion\": \"Test completion\", \"stop_reason\": \"max_tokens\"}`)\n\n\tresult, err := response.ParseResponse(rawResponse)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"Test completion\", result)\n\n\tinvalidResponse := []byte(`{\"completion\": \"Test completion\", \"invalid_json\":]`)\n\t_, err = response.ParseResponse(invalidResponse)\n\tassert.Error(t, err)\n}\n\nfunc TestAI21Response_ParseResponse(t *testing.T) {\n\tresponse := &AI21Response{}\n\trawResponse := []byte(`{\"completions\": [{\"data\": {\"text\": \"AI21 test\"}}], \"id\": \"123\"}`)\n\n\tresult, err := response.ParseResponse(rawResponse)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"AI21 test\", result)\n\n\tinvalidResponse := []byte(`{\"completions\": [{\"data\": {\"text\": \"AI21 test\"}}, \"invalid_json\":]`)\n\t_, err = response.ParseResponse(invalidResponse)\n\tassert.Error(t, err)\n}\n\nfunc TestAmazonResponse_ParseResponse(t *testing.T) {\n\tresponse := &AmazonResponse{}\n\trawResponse := []byte(`{\"inputTextTokenCount\": 10, \"results\": [{\"tokenCount\": 20, \"outputText\": \"Amazon test\", \"completionReason\": \"stop\"}]}`)\n\n\tresult, err := response.ParseResponse(rawResponse)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"Amazon test\", result)\n\n\tinvalidResponse := []byte(`{\"inputTextTokenCount\": 10, \"results\": [{\"tokenCount\": 20, \"outputText\": \"Amazon test\", \"invalid_json\":]`)\n\t_, err = response.ParseResponse(invalidResponse)\n\tassert.Error(t, err)\n}\n\nfunc TestNovaResponse_ParseResponse(t *testing.T) {\n\tresponse := &NovaResponse{}\n\trawResponse := []byte(`{\"output\": {\"message\": {\"content\": [{\"text\": \"Nova test\"}]}}, \"stopReason\": \"stop\", \"usage\": {\"inputTokens\": 10, \"outputTokens\": 20, \"totalTokens\": 30, \"cacheReadInputTokenCount\": 5}}`)\n\n\tresult, err := response.ParseResponse(rawResponse)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"Nova test\", result)\n\n\trawResponseEmptyContent := []byte(`{\"output\": {\"message\": {\"content\": []}}, \"stopReason\": \"stop\", \"usage\": {\"inputTokens\": 10, \"outputTokens\": 20, \"totalTokens\": 30, \"cacheReadInputTokenCount\": 5}}`)\n\n\tresultEmptyContent, errEmptyContent := response.ParseResponse(rawResponseEmptyContent)\n\tassert.NoError(t, errEmptyContent)\n\tassert.Equal(t, \"\", resultEmptyContent)\n\n\tinvalidResponse := []byte(`{\"output\": {\"message\": {\"content\": [{\"text\": \"Nova test\"}}, \"invalid_json\":]`)\n\t_, err = response.ParseResponse(invalidResponse)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/ai/cohere.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\tapi \"github.com/cohere-ai/cohere-go/v2\"\n\tcohere \"github.com/cohere-ai/cohere-go/v2/client\"\n\t\"github.com/cohere-ai/cohere-go/v2/option\"\n)\n\nconst cohereAIClientName = \"cohere\"\n\ntype CohereClient struct {\n\tnopCloser\n\n\tclient      *cohere.Client\n\tmodel       string\n\ttemperature float32\n\tmaxTokens   int\n}\n\nfunc (c *CohereClient) Configure(config IAIConfig) error {\n\ttoken := config.GetPassword()\n\n\topts := []option.RequestOption{\n\t\tcohere.WithToken(token),\n\t}\n\n\tbaseURL := config.GetBaseURL()\n\tif baseURL != \"\" {\n\t\topts = append(opts, cohere.WithBaseURL(baseURL))\n\t}\n\n\tclient := cohere.NewClient(opts...)\n\tif client == nil {\n\t\treturn errors.New(\"error creating Cohere client\")\n\t}\n\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\tc.maxTokens = config.GetMaxTokens()\n\n\treturn nil\n}\n\nfunc (c *CohereClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\t// Create a completion request\n\tresponse, err := c.client.Chat(ctx, &api.ChatRequest{\n\t\tMessage:      prompt,\n\t\tModel:        &c.model,\n\t\tK:            api.Int(0),\n\t\tPreamble:     api.String(\"\"),\n\t\tTemperature:  api.Float64(float64(c.temperature)),\n\t\tRawPrompting: api.Bool(false),\n\t\tMaxTokens:    api.Int(c.maxTokens),\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn response.Text, nil\n}\n\nfunc (c *CohereClient) GetName() string {\n\treturn cohereAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/customrest.go",
    "content": "package ai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst CustomRestClientName = \"customrest\"\n\ntype CustomRestClient struct {\n\tnopCloser\n\tclient      *http.Client\n\tbase        *url.URL\n\ttoken       string\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n\ttopK        int32\n}\n\ntype CustomRestRequest struct {\n\tModel string `json:\"model\"`\n\n\t// Prompt is the textual prompt to send to the model.\n\tPrompt string `json:\"prompt\"`\n\n\t// Options lists model-specific options. For example, temperature can be\n\t// set through this field, if the model supports it.\n\tOptions map[string]interface{} `json:\"options\"`\n}\n\ntype CustomRestResponse struct {\n\t// Model is the model name that generated the response.\n\tModel string `json:\"model\"`\n\n\t// CreatedAt is the timestamp of the response.\n\tCreatedAt time.Time `json:\"created_at\"`\n\n\t// Response is the textual response itself.\n\tResponse string `json:\"response\"`\n}\n\nfunc (c *CustomRestClient) Configure(config IAIConfig) error {\n\tbaseURL := config.GetBaseURL()\n\tif baseURL == \"\" {\n\t\tbaseURL = defaultBaseURL\n\t}\n\tc.token = config.GetPassword()\n\tbaseClientURL, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.base = baseClientURL\n\n\tproxyEndpoint := config.GetProxyEndpoint()\n\tc.client = http.DefaultClient\n\tif proxyEndpoint != \"\" {\n\t\tproxyUrl, err := url.Parse(proxyEndpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttransport := &http.Transport{\n\t\t\tProxy: http.ProxyURL(proxyUrl),\n\t\t}\n\n\t\tc.client = &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\t}\n\n\tc.model = config.GetModel()\n\tif c.model == \"\" {\n\t\tc.model = defaultModel\n\t}\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\treturn nil\n}\n\nfunc (c *CustomRestClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\tvar promptDetail struct {\n\t\tLanguage string `json:\"language,omitempty\"`\n\t\tMessage  string `json:\"message\"`\n\t\tPrompt   string `json:\"prompt,omitempty\"`\n\t}\n\tprompt = strings.NewReplacer(\"\\n\", \"\\\\n\", \"\\t\", \"\\\\t\").Replace(prompt)\n\tif err := json.Unmarshal([]byte(prompt), &promptDetail); err != nil {\n\t\treturn \"\", err\n\t}\n\tgenerateRequest := &CustomRestRequest{\n\t\tModel:  c.model,\n\t\tPrompt: promptDetail.Prompt,\n\t\tOptions: map[string]interface{}{\n\t\t\t\"temperature\": c.temperature,\n\t\t\t\"top_p\":       c.topP,\n\t\t\t\"top_k\":       c.topK,\n\t\t\t\"message\":     promptDetail.Message,\n\t\t\t\"language\":    promptDetail.Language,\n\t\t},\n\t}\n\trequestBody, err := json.Marshal(generateRequest)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\trequest, err := http.NewRequestWithContext(ctx, http.MethodPost, c.base.String(), bytes.NewBuffer(requestBody))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif c.token != \"\" {\n\t\trequest.Header.Set(\"Authorization\", \"Bearer \"+c.token)\n\t}\n\trequest.Header.Set(\"Content-Type\", \"application/json\")\n\trequest.Header.Set(\"Accept\", \"application/x-ndjson\")\n\n\tresponse, err := c.client.Do(request)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer response.Body.Close()\n\n\tresponseBody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not read response body: %w\", err)\n\t}\n\n\tif response.StatusCode >= http.StatusBadRequest {\n\t\treturn \"\", fmt.Errorf(\"Request Error, StatusCode: %d, ErrorMessage: %s\", response.StatusCode, responseBody)\n\t}\n\n\tvar result CustomRestResponse\n\tif err := json.Unmarshal(responseBody, &result); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn result.Response, nil\n}\n\nfunc (c *CustomRestClient) GetName() string {\n\treturn CustomRestClientName\n}\n"
  },
  {
    "path": "pkg/ai/factory.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"github.com/spf13/viper\"\n)\n\n// AIClientFactory is an interface for creating AI clients\ntype AIClientFactory interface {\n\tNewClient(provider string) IAI\n}\n\n// DefaultAIClientFactory is the default implementation of AIClientFactory\ntype DefaultAIClientFactory struct{}\n\n// NewClient creates a new AI client using the default implementation\nfunc (f *DefaultAIClientFactory) NewClient(provider string) IAI {\n\treturn NewClient(provider)\n}\n\n// ConfigProvider is an interface for accessing configuration\ntype ConfigProvider interface {\n\tUnmarshalKey(key string, rawVal interface{}) error\n}\n\n// ViperConfigProvider is the default implementation of ConfigProvider using Viper\ntype ViperConfigProvider struct{}\n\n// UnmarshalKey unmarshals a key from the configuration using Viper\nfunc (p *ViperConfigProvider) UnmarshalKey(key string, rawVal interface{}) error {\n\treturn viper.UnmarshalKey(key, rawVal)\n}\n\n// Default instances to be used\nvar (\n\tDefaultClientFactory = &DefaultAIClientFactory{}\n\tDefaultConfigProvider = &ViperConfigProvider{}\n)\n\n// For testing - these variables can be overridden in tests\nvar (\n\ttestAIClientFactory AIClientFactory = nil\n\ttestConfigProvider  ConfigProvider  = nil\n)\n\n// GetAIClientFactory returns the test factory if set, otherwise the default\nfunc GetAIClientFactory() AIClientFactory {\n\tif testAIClientFactory != nil {\n\t\treturn testAIClientFactory\n\t}\n\treturn DefaultClientFactory\n}\n\n// GetConfigProvider returns the test provider if set, otherwise the default\nfunc GetConfigProvider() ConfigProvider {\n\tif testConfigProvider != nil {\n\t\treturn testConfigProvider\n\t}\n\treturn DefaultConfigProvider\n}\n\n// For testing - set the test implementations\nfunc SetTestAIClientFactory(factory AIClientFactory) {\n\ttestAIClientFactory = factory\n}\n\nfunc SetTestConfigProvider(provider ConfigProvider) {\n\ttestConfigProvider = provider\n}\n\n// Reset test implementations\nfunc ResetTestImplementations() {\n\ttestAIClientFactory = nil\n\ttestConfigProvider = nil\n}"
  },
  {
    "path": "pkg/ai/googlegenai.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/google/generative-ai-go/genai\"\n\t\"google.golang.org/api/option\"\n)\n\nconst googleAIClientName = \"google\"\n\ntype GoogleGenAIClient struct {\n\tclient *genai.Client\n\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n\ttopK        int32\n\tmaxTokens   int\n}\n\nfunc (c *GoogleGenAIClient) Configure(config IAIConfig) error {\n\tctx := context.Background()\n\n\t// Access your API key as an environment variable (see \"Set up your API key\" above)\n\ttoken := config.GetPassword()\n\tauthOption := option.WithAPIKey(token)\n\tif token[0] == '{' {\n\t\tauthOption = option.WithCredentialsJSON([]byte(token))\n\t}\n\n\tclient, err := genai.NewClient(ctx, authOption)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating genai Google SDK client: %w\", err)\n\t}\n\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\tc.maxTokens = config.GetMaxTokens()\n\treturn nil\n}\n\nfunc (c *GoogleGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\t// Available models are at https://ai.google.dev/models e.g.gemini-pro.\n\tmodel := c.client.GenerativeModel(c.model)\n\tmodel.SetTemperature(c.temperature)\n\tmodel.SetTopP(c.topP)\n\tmodel.SetTopK(c.topK)\n\tmodel.SetMaxOutputTokens(int32(c.maxTokens))\n\n\t// Google AI SDK is capable of different inputs than just text, for now set explicit text prompt type.\n\t// Similarly, we could stream the response. For now k8sgpt does not support streaming.\n\tresp, err := model.GenerateContent(ctx, genai.Text(prompt))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(resp.Candidates) == 0 {\n\t\tif resp.PromptFeedback.BlockReason == genai.BlockReasonSafety {\n\t\t\tfor _, r := range resp.PromptFeedback.SafetyRatings {\n\t\t\t\tif !r.Blocked {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn \"\", fmt.Errorf(\"completion blocked due to %v with probability %v\", r.Category.String(), r.Probability.String())\n\t\t\t}\n\t\t}\n\t\treturn \"\", errors.New(\"no completion returned; unknown reason\")\n\t}\n\n\t// Format output.\n\t// TODO(bwplotka): Provider richer output in certain cases e.g. suddenly finished\n\t// completion based on finish reasons or safety rankings.\n\tgot := resp.Candidates[0]\n\tvar output string\n\tfor _, part := range got.Content.Parts {\n\t\tswitch o := part.(type) {\n\t\tcase genai.Text:\n\t\t\toutput += string(o)\n\t\t\toutput += \"\\n\"\n\t\tdefault:\n\t\t\tcolor.Yellow(\"found unsupported AI response part of type %T; ignoring\", part)\n\t\t}\n\t}\n\n\tif got.CitationMetadata != nil && len(got.CitationMetadata.CitationSources) > 0 {\n\t\toutput += \"Citations:\\n\"\n\t\tfor _, source := range got.CitationMetadata.CitationSources {\n\t\t\t// TODO(bwplotka): Give details around what exactly words could be attributed to the citation.\n\t\t\toutput += fmt.Sprintf(\"* %s, %s\\n\", *source.URI, source.License)\n\t\t}\n\t}\n\treturn output, nil\n}\n\nfunc (c *GoogleGenAIClient) GetName() string {\n\treturn googleAIClientName\n}\n\nfunc (c *GoogleGenAIClient) Close() {\n\tif err := c.client.Close(); err != nil {\n\t\tcolor.Red(\"googleai client close error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/ai/googlevertexai.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"cloud.google.com/go/vertexai/genai\"\n\t\"github.com/fatih/color\"\n)\n\nconst googleVertexAIClientName = \"googlevertexai\"\n\ntype GoogleVertexAIClient struct {\n\tclient *genai.Client\n\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n\ttopK        int32\n\tmaxTokens   int\n}\n\n// Vertex AI Gemini supported Regions\n// https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini\nconst VERTEXAI_DEFAULT_REGION = \"us-central1\" // default use us-east-1 region\n\nconst (\n\tUS_Central_1             = \"us-central1\"\n\tUS_West_4                = \"us-west4\"\n\tNorth_America_Northeast1 = \"northamerica-northeast1\"\n\tUS_East_4                = \"us-east4\"\n\tUS_West_1                = \"us-west1\"\n\tAsia_Northeast_3         = \"asia-northeast3\"\n\tAsia_Southeast_1         = \"asia-southeast1\"\n\tAsia_Northeast_1         = \"asia-northeast1\"\n)\n\nvar VERTEXAI_SUPPORTED_REGION = []string{\n\tUS_Central_1,\n\tUS_West_4,\n\tNorth_America_Northeast1,\n\tUS_East_4,\n\tUS_West_1,\n\tAsia_Northeast_3,\n\tAsia_Southeast_1,\n\tAsia_Northeast_1,\n}\n\nconst (\n\tModelGeminiProV1       = \"gemini-1.0-pro-001\"    // Retired Model\n\tModelGeminiProV2_5     = \"gemini-2.5-pro\"        // Latest Stable Model\n\tModelGeminiFlashV2_5   = \"gemini-2.5-flash\"      // Latest Stable Model\n\tModelGeminiFlashV2     = \"gemini-2.0-flash\"      // Latest Stable Model\n\tModelGeminiFlashLiteV2 = \"gemini-2.0-flash-lite\" // Latest Stable Model\n\tModelGeminiProV1_5     = \"gemini-1.5-pro-002*\"   // Legacy Stable Model\n\tModelGeminiFlashV1_5   = \"gemini-1.5-flash-002*\" // Legacy Stable Model\n)\n\nvar VERTEXAI_MODELS = []string{\n\tModelGeminiProV2_5,\n\tModelGeminiFlashV2_5,\n\tModelGeminiFlashV2,\n\tModelGeminiFlashLiteV2,\n\tModelGeminiProV1_5,\n\tModelGeminiFlashV1_5,\n\tModelGeminiProV1,\n}\n\n// GetModelOrDefault check config model\nfunc GetVertexAIModelOrDefault(model string) string {\n\n\t// Check if the provided model is in the list\n\tfor _, m := range VERTEXAI_MODELS {\n\t\tif m == model {\n\t\t\treturn model // Return the provided model\n\t\t}\n\t}\n\n\t// Return the default model if the provided model is not in the list\n\treturn VERTEXAI_MODELS[0]\n}\n\n// GetModelOrDefault check config region\nfunc GetVertexAIRegionOrDefault(region string) string {\n\n\t// Check if the provided model is in the list\n\tfor _, m := range VERTEXAI_SUPPORTED_REGION {\n\t\tif m == region {\n\t\t\treturn region // Return the provided model\n\t\t}\n\t}\n\n\t// Return the default model if the provided model is not in the list\n\treturn VERTEXAI_DEFAULT_REGION\n}\n\nfunc (g *GoogleVertexAIClient) Configure(config IAIConfig) error {\n\tctx := context.Background()\n\n\t// Currently you can access VertexAI either by being authenticated via OAuth or Bearer token so we need to consider both\n\tprojectId := config.GetProviderId()\n\tregion := GetVertexAIRegionOrDefault(config.GetProviderRegion())\n\n\tclient, err := genai.NewClient(ctx, projectId, region)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating genai Google SDK client: %w\", err)\n\t}\n\n\tg.client = client\n\tg.model = GetVertexAIModelOrDefault(config.GetModel())\n\tg.temperature = config.GetTemperature()\n\tg.topP = config.GetTopP()\n\tg.topK = config.GetTopK()\n\tg.maxTokens = config.GetMaxTokens()\n\n\treturn nil\n}\n\nfunc (g *GoogleVertexAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\n\tmodel := g.client.GenerativeModel(g.model)\n\tmodel.SetTemperature(g.temperature)\n\tmodel.SetTopP(g.topP)\n\tmodel.SetTopK(g.topK)\n\tmodel.SetMaxOutputTokens(int32(g.maxTokens))\n\n\t// Google AI SDK is capable of different inputs than just text, for now set explicit text prompt type.\n\t// Similarly, we could stream the response. For now k8sgpt does not support streaming.\n\tresp, err := model.GenerateContent(ctx, genai.Text(prompt))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(resp.Candidates) == 0 {\n\t\tif resp.PromptFeedback.BlockReason > 0 {\n\t\t\tfor _, r := range resp.PromptFeedback.SafetyRatings {\n\t\t\t\tif !r.Blocked {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn \"\", fmt.Errorf(\"completion blocked due to %v with probability %v\", r.Category.String(), r.Probability.String())\n\t\t\t}\n\t\t}\n\t\treturn \"\", errors.New(\"no completion returned; unknown reason\")\n\t}\n\n\t// Format output.\n\t// TODO(bwplotka): Provider richer output in certain cases e.g. suddenly finished\n\t// completion based on finish reasons or safety rankings.\n\tgot := resp.Candidates[0]\n\tvar output string\n\tfor _, part := range got.Content.Parts {\n\t\tswitch o := part.(type) {\n\t\tcase genai.Text:\n\t\t\toutput += string(o)\n\t\t\toutput += \"\\n\"\n\t\tdefault:\n\t\t\tcolor.Yellow(\"found unsupported AI response part of type %T; ignoring\", part)\n\t\t}\n\t}\n\n\tif got.CitationMetadata != nil && len(got.CitationMetadata.Citations) > 0 {\n\t\toutput += \"Citations:\\n\"\n\t\tfor _, source := range got.CitationMetadata.Citations {\n\t\t\t// TODO(bwplotka): Give details around what exactly words could be attributed to the citation.\n\t\t\toutput += fmt.Sprintf(\"* %s, %s\\n\", source.URI, source.License)\n\t\t}\n\t}\n\treturn output, nil\n}\n\nfunc (g *GoogleVertexAIClient) GetName() string {\n\treturn googleVertexAIClientName\n}\n\nfunc (g *GoogleVertexAIClient) Close() {\n\tif err := g.client.Close(); err != nil {\n\t\tcolor.Red(\"googleai client close error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/ai/groq.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nconst groqAIClientName = \"groq\"\n\n// Default Groq API endpoint (OpenAI-compatible)\nconst groqAPIBaseURL = \"https://api.groq.com/openai/v1\"\n\ntype GroqClient struct {\n\tnopCloser\n\n\tclient      *openai.Client\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n}\n\nfunc (c *GroqClient) Configure(config IAIConfig) error {\n\ttoken := config.GetPassword()\n\tdefaultConfig := openai.DefaultConfig(token)\n\tproxyEndpoint := config.GetProxyEndpoint()\n\n\tbaseURL := config.GetBaseURL()\n\tif baseURL != \"\" {\n\t\tdefaultConfig.BaseURL = baseURL\n\t} else {\n\t\tdefaultConfig.BaseURL = groqAPIBaseURL\n\t}\n\n\ttransport := &http.Transport{}\n\tif proxyEndpoint != \"\" {\n\t\tproxyUrl, err := url.Parse(proxyEndpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttransport.Proxy = http.ProxyURL(proxyUrl)\n\t}\n\n\tcustomHeaders := config.GetCustomHeaders()\n\tdefaultConfig.HTTPClient = &http.Client{\n\t\tTransport: &OpenAIHeaderTransport{\n\t\t\tOrigin:  transport,\n\t\t\tHeaders: customHeaders,\n\t\t},\n\t}\n\n\tclient := openai.NewClientWithConfig(defaultConfig)\n\tif client == nil {\n\t\treturn errors.New(\"error creating Groq client\")\n\t}\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\treturn nil\n}\n\nfunc (c *GroqClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\tresp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{\n\t\tModel: c.model,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    \"user\",\n\t\t\t\tContent: prompt,\n\t\t\t},\n\t\t},\n\t\tTemperature:      c.temperature,\n\t\tMaxTokens:        maxToken,\n\t\tPresencePenalty:  presencePenalty,\n\t\tFrequencyPenalty: frequencyPenalty,\n\t\tTopP:             c.topP,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resp.Choices[0].Message.Content, nil\n}\n\nfunc (c *GroqClient) GetName() string {\n\treturn groqAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/huggingface.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\n\t\"github.com/hupe1980/go-huggingface\"\n\t\"k8s.io/utils/ptr\"\n)\n\nconst huggingfaceAIClientName = \"huggingface\"\n\ntype HuggingfaceClient struct {\n\tnopCloser\n\n\tclient      *huggingface.InferenceClient\n\tmodel       string\n\ttopP        float32\n\ttopK        int32\n\ttemperature float32\n\tmaxTokens   int\n}\n\nfunc (c *HuggingfaceClient) Configure(config IAIConfig) error {\n\ttoken := config.GetPassword()\n\n\tclient := huggingface.NewInferenceClient(token)\n\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\tc.temperature = config.GetTemperature()\n\tif config.GetMaxTokens() > 500 {\n\t\tc.maxTokens = 500\n\t} else {\n\t\tc.maxTokens = config.GetMaxTokens()\n\t}\n\treturn nil\n}\n\nfunc (c *HuggingfaceClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\tresp, err := c.client.Conversational(ctx, &huggingface.ConversationalRequest{\n\t\tInputs: huggingface.ConverstationalInputs{\n\t\t\tText: prompt,\n\t\t},\n\t\tModel: c.model,\n\t\tParameters: huggingface.ConversationalParameters{\n\t\t\tTopP:        ptr.To[float64](float64(c.topP)),\n\t\t\tTopK:        ptr.To[int](int(c.topK)),\n\t\t\tTemperature: ptr.To[float64](float64(c.temperature)),\n\t\t\tMaxLength:   &c.maxTokens,\n\t\t},\n\t\tOptions: huggingface.Options{\n\t\t\tWaitForModel: ptr.To[bool](true),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resp.GeneratedText, nil\n}\n\nfunc (c *HuggingfaceClient) GetName() string { return huggingfaceAIClientName }\n"
  },
  {
    "path": "pkg/ai/iai.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n)\n\nvar (\n\tclients = []IAI{\n\t\t&OpenAIClient{},\n\t\t&AzureAIClient{},\n\t\t&LocalAIClient{},\n\t\t&OllamaClient{},\n\t\t&NoOpAIClient{},\n\t\t&CohereClient{},\n\t\t&AmazonBedRockClient{},\n\t\t&SageMakerAIClient{},\n\t\t&GoogleGenAIClient{},\n\t\t&HuggingfaceClient{},\n\t\t&GoogleVertexAIClient{},\n\t\t&OCIGenAIClient{},\n\t\t&CustomRestClient{},\n\t\t&IBMWatsonxAIClient{},\n\t\t&GroqClient{},\n\t}\n\tBackends = []string{\n\t\topenAIClientName,\n\t\tlocalAIClientName,\n\t\tollamaClientName,\n\t\tazureAIClientName,\n\t\tcohereAIClientName,\n\t\tamazonbedrockAIClientName,\n\t\tamazonsagemakerAIClientName,\n\t\tgoogleAIClientName,\n\t\tnoopAIClientName,\n\t\thuggingfaceAIClientName,\n\t\tgoogleVertexAIClientName,\n\t\tociClientName,\n\t\tCustomRestClientName,\n\t\tibmWatsonxAIClientName,\n\t\tgroqAIClientName,\n\t}\n)\n\n// IAI is an interface all clients (representing backends) share.\ntype IAI interface {\n\t// Configure sets up client for given configuration. This is expected to be\n\t// executed once per client life-time (e.g. analysis CLI command invocation).\n\tConfigure(config IAIConfig) error\n\t// GetCompletion generates text based on prompt.\n\tGetCompletion(ctx context.Context, prompt string) (string, error)\n\t// GetName returns name of the backend/client.\n\tGetName() string\n\t// Close cleans all the resources. No other methods should be used on the\n\t// objects after this method is invoked.\n\tClose()\n}\n\ntype nopCloser struct{}\n\nfunc (nopCloser) Close() {}\n\ntype IAIConfig interface {\n\tGetPassword() string\n\tGetModel() string\n\tGetBaseURL() string\n\tGetProxyEndpoint() string\n\tGetEndpointName() string\n\tGetEngine() string\n\tGetTemperature() float32\n\tGetProviderRegion() string\n\tGetTopP() float32\n\tGetTopK() int32\n\tGetMaxTokens() int\n\tGetProviderId() string\n\tGetCompartmentId() string\n\tGetOrganizationId() string\n\tGetCustomHeaders() []http.Header\n}\n\nfunc NewClient(provider string) IAI {\n\tfor _, c := range clients {\n\t\tif provider == c.GetName() {\n\t\t\treturn c\n\t\t}\n\t}\n\t// default client\n\treturn &OpenAIClient{}\n}\n\ntype AIConfiguration struct {\n\tProviders       []AIProvider `mapstructure:\"providers\"`\n\tDefaultProvider string       `mapstructure:\"defaultprovider\"`\n}\n\ntype AIProvider struct {\n\tName           string        `mapstructure:\"name\"`\n\tModel          string        `mapstructure:\"model\"`\n\tPassword       string        `mapstructure:\"password\" yaml:\"password,omitempty\"`\n\tBaseURL        string        `mapstructure:\"baseurl\" yaml:\"baseurl,omitempty\"`\n\tProxyEndpoint  string        `mapstructure:\"proxyEndpoint\" yaml:\"proxyEndpoint,omitempty\"`\n\tProxyPort      string        `mapstructure:\"proxyPort\" yaml:\"proxyPort,omitempty\"`\n\tEndpointName   string        `mapstructure:\"endpointname\" yaml:\"endpointname,omitempty\"`\n\tEngine         string        `mapstructure:\"engine\" yaml:\"engine,omitempty\"`\n\tTemperature    float32       `mapstructure:\"temperature\" yaml:\"temperature,omitempty\"`\n\tProviderRegion string        `mapstructure:\"providerregion\" yaml:\"providerregion,omitempty\"`\n\tProviderId     string        `mapstructure:\"providerid\" yaml:\"providerid,omitempty\"`\n\tCompartmentId  string        `mapstructure:\"compartmentid\" yaml:\"compartmentid,omitempty\"`\n\tTopP           float32       `mapstructure:\"topp\" yaml:\"topp,omitempty\"`\n\tTopK           int32         `mapstructure:\"topk\" yaml:\"topk,omitempty\"`\n\tMaxTokens      int           `mapstructure:\"maxtokens\" yaml:\"maxtokens,omitempty\"`\n\tOrganizationId string        `mapstructure:\"organizationid\" yaml:\"organizationid,omitempty\"`\n\tCustomHeaders  []http.Header `mapstructure:\"customHeaders\"`\n}\n\nfunc (p *AIProvider) GetBaseURL() string {\n\treturn p.BaseURL\n}\n\nfunc (p *AIProvider) GetProxyEndpoint() string {\n\treturn p.ProxyEndpoint\n}\n\nfunc (p *AIProvider) GetEndpointName() string {\n\treturn p.EndpointName\n}\n\nfunc (p *AIProvider) GetTopP() float32 {\n\treturn p.TopP\n}\n\nfunc (p *AIProvider) GetTopK() int32 {\n\treturn p.TopK\n}\n\nfunc (p *AIProvider) GetMaxTokens() int {\n\treturn p.MaxTokens\n}\n\nfunc (p *AIProvider) GetPassword() string {\n\treturn p.Password\n}\n\nfunc (p *AIProvider) GetModel() string {\n\treturn p.Model\n}\n\nfunc (p *AIProvider) GetEngine() string {\n\treturn p.Engine\n}\nfunc (p *AIProvider) GetTemperature() float32 {\n\treturn p.Temperature\n}\n\nfunc (p *AIProvider) GetProviderRegion() string {\n\treturn p.ProviderRegion\n}\n\nfunc (p *AIProvider) GetProviderId() string {\n\treturn p.ProviderId\n}\n\nfunc (p *AIProvider) GetCompartmentId() string {\n\treturn p.CompartmentId\n}\n\nfunc (p *AIProvider) GetOrganizationId() string {\n\treturn p.OrganizationId\n}\n\nfunc (p *AIProvider) GetCustomHeaders() []http.Header {\n\treturn p.CustomHeaders\n}\n\nvar passwordlessProviders = []string{\"localai\", \"ollama\", \"amazonsagemaker\", \"amazonbedrock\", \"googlevertexai\", \"oci\", \"customrest\"}\n\nfunc NeedPassword(backend string) bool {\n\tfor _, b := range passwordlessProviders {\n\t\tif b == backend {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/ai/interactive/interactive.go",
    "content": "package interactive\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analysis\"\n\t\"github.com/pterm/pterm\"\n)\n\ntype INTERACTIVE_STATE int\n\nconst (\n\tprompt = \"Given the following context: \"\n)\n\nconst (\n\tE_RUNNING INTERACTIVE_STATE = iota\n\tE_EXITED                    = iota\n)\n\ntype InteractionRunner struct {\n\tconfig        *analysis.Analysis\n\tState         chan INTERACTIVE_STATE\n\tcontextWindow []byte\n}\n\nfunc NewInteractionRunner(config *analysis.Analysis, contextWindow []byte) *InteractionRunner {\n\treturn &InteractionRunner{\n\t\tconfig:        config,\n\t\tcontextWindow: contextWindow,\n\t\tState:         make(chan INTERACTIVE_STATE),\n\t}\n}\n\nfunc (a *InteractionRunner) StartInteraction() {\n\ta.State <- E_RUNNING\n\tpterm.Println(\"Interactive mode enabled [type exit to close.]\")\n\tfor {\n\n\t\tquery := pterm.DefaultInteractiveTextInput.WithMultiLine(false)\n\t\tqueryString, err := query.Show()\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tif queryString == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(queryString, \"exit\") {\n\t\t\ta.State <- E_EXITED\n\t\t\tcontinue\n\t\t}\n\t\tpterm.Println()\n\t\tcontextWindow := fmt.Sprintf(\"%s %s %s\", prompt, string(a.contextWindow),\n\t\t\tqueryString)\n\n\t\tresponse, err := a.config.AIClient.GetCompletion(a.config.Context,\n\t\t\tcontextWindow)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error: %v\", err)\n\t\t\ta.State <- E_EXITED\n\t\t\tcontinue\n\t\t}\n\t\tpterm.Println(response)\n\t}\n}\n"
  },
  {
    "path": "pkg/ai/localai.go",
    "content": "package ai\n\nconst localAIClientName = \"localai\"\n\ntype LocalAIClient struct {\n\tOpenAIClient\n}\n\nfunc (a *LocalAIClient) GetName() string {\n\treturn localAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/noopai.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n)\n\nconst noopAIClientName = \"noopai\"\n\ntype NoOpAIClient struct {\n\tnopCloser\n}\n\nfunc (c *NoOpAIClient) Configure(_ IAIConfig) error {\n\treturn nil\n}\n\nfunc (c *NoOpAIClient) GetCompletion(_ context.Context, prompt string) (string, error) {\n\tresponse := \"I am a noop response to the prompt \" + prompt\n\treturn response, nil\n}\n\nfunc (c *NoOpAIClient) GetName() string {\n\treturn noopAIClientName\n}\n"
  },
  {
    "path": "pkg/ai/ocigenai.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/oracle/oci-go-sdk/v65/common\"\n\t\"github.com/oracle/oci-go-sdk/v65/generativeai\"\n\t\"github.com/oracle/oci-go-sdk/v65/generativeaiinference\"\n\t\"reflect\"\n)\n\nconst ociClientName = \"oci\"\n\ntype ociModelVendor string\n\nconst (\n\tvendorCohere = \"cohere\"\n\tvendorMeta   = \"meta\"\n)\n\ntype OCIGenAIClient struct {\n\tnopCloser\n\n\tclient        *generativeaiinference.GenerativeAiInferenceClient\n\tmodel         *generativeai.Model\n\tmodelID       string\n\tcompartmentId string\n\ttemperature   float32\n\ttopP          float32\n\ttopK          int32\n\tmaxTokens     int\n}\n\nfunc (c *OCIGenAIClient) GetName() string {\n\treturn ociClientName\n}\n\nfunc (c *OCIGenAIClient) Configure(config IAIConfig) error {\n\tconfig.GetEndpointName()\n\tc.modelID = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\tc.maxTokens = config.GetMaxTokens()\n\tc.compartmentId = config.GetCompartmentId()\n\tprovider := common.DefaultConfigProvider()\n\tclient, err := generativeaiinference.NewGenerativeAiInferenceClientWithConfigurationProvider(provider)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.client = &client\n\tmodel, err := c.getModel(provider)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.model = model\n\treturn nil\n}\n\nfunc (c *OCIGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\trequest := c.newChatRequest(prompt)\n\tresponse, err := c.client.Chat(ctx, request)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn extractGeneratedText(response.ChatResponse)\n}\n\nfunc (c *OCIGenAIClient) newChatRequest(prompt string) generativeaiinference.ChatRequest {\n\treturn generativeaiinference.ChatRequest{\n\t\tChatDetails: generativeaiinference.ChatDetails{\n\t\t\tCompartmentId: &c.compartmentId,\n\t\t\tServingMode:   c.getServingMode(),\n\t\t\tChatRequest:   c.getChatModelRequest(prompt),\n\t\t},\n\t}\n}\n\nfunc (c *OCIGenAIClient) getChatModelRequest(prompt string) generativeaiinference.BaseChatRequest {\n\ttemperatureF64 := float64(c.temperature)\n\ttopPF64 := float64(c.topP)\n\ttopK := int(c.topK)\n\n\tswitch c.getVendor() {\n\tcase vendorMeta:\n\t\tmessages := []generativeaiinference.Message{\n\t\t\tgenerativeaiinference.UserMessage{\n\t\t\t\tContent: []generativeaiinference.ChatContent{\n\t\t\t\t\tgenerativeaiinference.TextContent{\n\t\t\t\t\t\tText: &prompt,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\t// 0 is invalid for Meta vendor type, instead use -1 to disable topK sampling.\n\t\tif topK == 0 {\n\t\t\ttopK = -1\n\t\t}\n\t\treturn generativeaiinference.GenericChatRequest{\n\t\t\tMessages:    messages,\n\t\t\tTopK:        &topK,\n\t\t\tTopP:        &topPF64,\n\t\t\tTemperature: &temperatureF64,\n\t\t\tMaxTokens:   &c.maxTokens,\n\t\t}\n\tdefault: // Default to cohere\n\t\treturn generativeaiinference.CohereChatRequest{\n\t\t\tMessage:     &prompt,\n\t\t\tMaxTokens:   &c.maxTokens,\n\t\t\tTemperature: &temperatureF64,\n\t\t\tTopK:        &topK,\n\t\t\tTopP:        &topPF64,\n\t\t}\n\n\t}\n}\n\nfunc extractGeneratedText(llmInferenceResponse generativeaiinference.BaseChatResponse) (string, error) {\n\tswitch response := llmInferenceResponse.(type) {\n\tcase generativeaiinference.GenericChatResponse:\n\t\tif len(response.Choices) > 0 && len(response.Choices[0].Message.GetContent()) > 0 {\n\t\t\tif content, ok := response.Choices[0].Message.GetContent()[0].(generativeaiinference.TextContent); ok {\n\t\t\t\treturn *content.Text, nil\n\t\t\t}\n\t\t}\n\t\treturn \"\", errors.New(\"no text found in oci response\")\n\tcase generativeaiinference.CohereChatResponse:\n\t\treturn *response.Text, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unknown oci response type: %s\", reflect.TypeOf(llmInferenceResponse).Name())\n\t}\n}\n\nfunc (c *OCIGenAIClient) getServingMode() generativeaiinference.ServingMode {\n\tif c.isBaseModel() {\n\t\treturn generativeaiinference.OnDemandServingMode{\n\t\t\tModelId: &c.modelID,\n\t\t}\n\t}\n\treturn generativeaiinference.DedicatedServingMode{\n\t\tEndpointId: &c.modelID,\n\t}\n}\n\nfunc (c *OCIGenAIClient) getModel(provider common.ConfigurationProvider) (*generativeai.Model, error) {\n\tclient, err := generativeai.NewGenerativeAiClientWithConfigurationProvider(provider)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresponse, err := client.GetModel(context.Background(), generativeai.GetModelRequest{\n\t\tModelId: &c.modelID,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &response.Model, nil\n}\n\nfunc (c *OCIGenAIClient) isBaseModel() bool {\n\treturn c.model != nil && c.model.Type == generativeai.ModelTypeBase\n}\n\nfunc (c *OCIGenAIClient) getVendor() ociModelVendor {\n\tif c.model == nil || c.model.Vendor == nil {\n\t\treturn \"\"\n\t}\n\treturn ociModelVendor(*c.model.Vendor)\n}\n"
  },
  {
    "path": "pkg/ai/ollama.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\n\tollama \"github.com/ollama/ollama/api\"\n)\n\nconst ollamaClientName = \"ollama\"\n\ntype OllamaClient struct {\n\tnopCloser\n\n\tclient      *ollama.Client\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n}\n\nconst (\n\tdefaultBaseURL = \"http://localhost:11434\"\n\tdefaultModel   = \"llama3\"\n)\n\nfunc (c *OllamaClient) Configure(config IAIConfig) error {\n\tbaseURL := config.GetBaseURL()\n\tif baseURL == \"\" {\n\t\tbaseURL = defaultBaseURL\n\t}\n\tbaseClientURL, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tproxyEndpoint := config.GetProxyEndpoint()\n\thttpClient := http.DefaultClient\n\tif proxyEndpoint != \"\" {\n\t\tproxyUrl, err := url.Parse(proxyEndpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttransport := &http.Transport{\n\t\t\tProxy: http.ProxyURL(proxyUrl),\n\t\t}\n\n\t\thttpClient = &http.Client{\n\t\t\tTransport: transport,\n\t\t}\n\t}\n\n\tc.client = ollama.NewClient(baseClientURL, httpClient)\n\tif c.client == nil {\n\t\treturn errors.New(\"error creating Ollama client\")\n\t}\n\tc.model = config.GetModel()\n\tif c.model == \"\" {\n\t\tc.model = defaultModel\n\t}\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\treturn nil\n}\nfunc (c *OllamaClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\treq := &ollama.GenerateRequest{\n\t\tModel:  c.model,\n\t\tPrompt: prompt,\n\t\tStream: new(bool),\n\t\tOptions: map[string]interface{}{\n\t\t\t\"temperature\": c.temperature,\n\t\t\t\"top_p\":       c.topP,\n\t\t},\n\t}\n\tcompletion := \"\"\n\trespFunc := func(resp ollama.GenerateResponse) error {\n\t\tcompletion = resp.Response\n\t\treturn nil\n\t}\n\terr := c.client.Generate(ctx, req, respFunc)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn completion, nil\n}\nfunc (a *OllamaClient) GetName() string {\n\treturn ollamaClientName\n}\n"
  },
  {
    "path": "pkg/ai/openai.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/sashabaranov/go-openai\"\n)\n\nconst openAIClientName = \"openai\"\n\ntype OpenAIClient struct {\n\tnopCloser\n\n\tclient      *openai.Client\n\tmodel       string\n\ttemperature float32\n\ttopP        float32\n\t// organizationId string\n}\n\nconst (\n\t// OpenAI completion parameters\n\tmaxToken         = 2048\n\tpresencePenalty  = 0.0\n\tfrequencyPenalty = 0.0\n)\n\nfunc (c *OpenAIClient) Configure(config IAIConfig) error {\n\ttoken := config.GetPassword()\n\tdefaultConfig := openai.DefaultConfig(token)\n\torgId := config.GetOrganizationId()\n\tproxyEndpoint := config.GetProxyEndpoint()\n\n\tbaseURL := config.GetBaseURL()\n\tif baseURL != \"\" {\n\t\tdefaultConfig.BaseURL = baseURL\n\t}\n\n\ttransport := &http.Transport{}\n\tif proxyEndpoint != \"\" {\n\t\tproxyUrl, err := url.Parse(proxyEndpoint)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttransport.Proxy = http.ProxyURL(proxyUrl)\n\t}\n\n\tif orgId != \"\" {\n\t\tdefaultConfig.OrgID = orgId\n\t}\n\n\tcustomHeaders := config.GetCustomHeaders()\n\tdefaultConfig.HTTPClient = &http.Client{\n\t\tTransport: &OpenAIHeaderTransport{\n\t\t\tOrigin:  transport,\n\t\t\tHeaders: customHeaders,\n\t\t},\n\t}\n\n\tclient := openai.NewClientWithConfig(defaultConfig)\n\tif client == nil {\n\t\treturn errors.New(\"error creating OpenAI client\")\n\t}\n\tc.client = client\n\tc.model = config.GetModel()\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\treturn nil\n}\n\nfunc (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\t// Create a completion request\n\tresp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{\n\t\tModel: c.model,\n\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t{\n\t\t\t\tRole:    \"user\",\n\t\t\t\tContent: prompt,\n\t\t\t},\n\t\t},\n\t\tTemperature:      c.temperature,\n\t\tMaxCompletionTokens: maxToken,\n\t\tPresencePenalty:  presencePenalty,\n\t\tFrequencyPenalty: frequencyPenalty,\n\t\tTopP:             c.topP,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resp.Choices[0].Message.Content, nil\n}\n\nfunc (c *OpenAIClient) GetName() string {\n\treturn openAIClientName\n}\n\n// OpenAIHeaderTransport is an http.RoundTripper that adds the given headers to each request.\ntype OpenAIHeaderTransport struct {\n\tOrigin  http.RoundTripper\n\tHeaders []http.Header\n}\n\n// RoundTrip implements the http.RoundTripper interface.\nfunc (t *OpenAIHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\t// Clone the request to avoid modifying the original request\n\tclonedReq := req.Clone(req.Context())\n\tfor _, header := range t.Headers {\n\t\tfor key, values := range header {\n\t\t\t// Possible values per header:  RFC 2616\n\t\t\tfor _, value := range values {\n\t\t\t\tclonedReq.Header.Add(key, value)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn t.Origin.RoundTrip(clonedReq)\n}\n"
  },
  {
    "path": "pkg/ai/openai_header_transport_test.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Mock configuration\ntype mockConfig struct {\n\tbaseURL string\n}\n\nfunc (m *mockConfig) GetPassword() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetOrganizationId() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetProxyEndpoint() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetBaseURL() string {\n\treturn m.baseURL\n}\n\nfunc (m *mockConfig) GetCustomHeaders() []http.Header {\n\treturn []http.Header{\n\t\t{\"X-Custom-Header-1\": []string{\"Value1\"}},\n\t\t{\"X-Custom-Header-2\": []string{\"Value2\"}},\n\t\t{\"X-Custom-Header-2\": []string{\"Value3\"}}, // Testing multiple values for the same header\n\t}\n}\n\nfunc (m *mockConfig) GetModel() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetTemperature() float32 {\n\treturn 0.0\n}\n\nfunc (m *mockConfig) GetTopP() float32 {\n\treturn 0.0\n}\nfunc (m *mockConfig) GetCompartmentId() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetTopK() int32 {\n\treturn 0.0\n}\n\nfunc (m *mockConfig) GetMaxTokens() int {\n\treturn 0\n}\n\nfunc (m *mockConfig) GetEndpointName() string {\n\treturn \"\"\n}\nfunc (m *mockConfig) GetEngine() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetProviderId() string {\n\treturn \"\"\n}\n\nfunc (m *mockConfig) GetProviderRegion() string {\n\treturn \"\"\n}\n\nfunc TestOpenAIClient_CustomHeaders(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tassert.Equal(t, \"Value1\", r.Header.Get(\"X-Custom-Header-1\"))\n\t\tassert.ElementsMatch(t, []string{\"Value2\", \"Value3\"}, r.Header[\"X-Custom-Header-2\"])\n\t\tw.WriteHeader(http.StatusOK)\n\t\t// Mock response for openai completion\n\t\tmockResponse := `{\"choices\": [{\"message\": {\"content\": \"test\"}}]}`\n\t\tn, err := w.Write([]byte(mockResponse))\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error writing response: %v\", err)\n\t\t}\n\t\tif n != len(mockResponse) {\n\t\t\tt.Fatalf(\"expected to write %d bytes but wrote %d bytes\", len(mockResponse), n)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\tconfig := &mockConfig{baseURL: server.URL}\n\n\tclient := &OpenAIClient{}\n\terr := client.Configure(config)\n\tassert.NoError(t, err)\n\n\t// Make a completion request to trigger the headers\n\tctx := context.Background()\n\t_, err = client.GetCompletion(ctx, \"foo prompt\")\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/ai/prompts.go",
    "content": "package ai\n\nconst (\n\tdefault_prompt = `Simplify the following Kubernetes error message delimited by triple dashes written in --- %s --- language; --- %s ---.\n\tProvide the most possible solution in a step by step style in no more than 280 characters. Write the output in the following format:\n\tError: {Explain error here}\n\tSolution: {Step by step solution here}\n\t`\n\n\tprom_conf_prompt = `Simplify the following Prometheus error message delimited by triple dashes written in --- %s --- language; --- %s ---.\n\tThis error came when validating the Prometheus configuration file.\n\tProvide step by step instructions to fix, with suggestions, referencing Prometheus documentation if relevant.\n\tWrite the output in the following format in no more than 300 characters:\n\tError: {Explain error here}\n\tSolution: {Step by step solution here}\n\t`\n\n\tprom_relabel_prompt = `\n\tReturn your prompt in this language: %s, beginning with\n\tThe following is a list of the form:\n\tjob_name:\n\t{Prometheus job_name}\n\trelabel_configs:\n\t{Prometheus relabel_configs}\n\tkubernetes_sd_configs:\n\t{Prometheus service discovery config}\n\t---\n\t%s\n\t---\n\tFor each job_name, describe the Kubernetes service and pod labels,\n\tnamespaces, ports, and containers they match.\n\tReturn the message:\n\tDiscovered and parsed Prometheus scrape configurations.\n\tFor targets to be scraped by Prometheus, ensure they are running with\n\tat least one of the following label sets:\n\tThen for each job, write this format:\n\t- Job: {job_name}\n\t  - Service Labels:\n\t    - {list of service labels}\n\t  - Pod Labels:\n\t    - {list of pod labels}\n\t  - Namespaces:\n\t    - {list of namespaces}\n\t  - Ports:\n\t    - {list of ports}\n\t  - Containers:\n\t    - {list of container names}\n\t`\n\n\tkyverno_prompt = `Simplify the following Kyverno warnings message delimited by triple dashes written in --- %s --- language; --- %s ---.\n\tProvide the most probable solution as a kubectl command. \n\n\tWrite the output in the following format, for the solution, only show the kubectl command:\n\t\n\tError: {Explain error here}\n\n\tSolution: {kubectl command}\n\t`\n\traw_promt = `{\"language\": \"%s\",\"message\": \"%s\",\"prompt\": \"%s\"}`\n)\n\nvar PromptMap = map[string]string{\n\t\"raw\":                           raw_promt,\n\t\"default\":                       default_prompt,\n\t\"PrometheusConfigValidate\":      prom_conf_prompt,\n\t\"PrometheusConfigRelabelReport\": prom_relabel_prompt,\n\t\"PolicyReport\":                  kyverno_prompt,\n\t\"ClusterPolicyReport\":           kyverno_prompt,\n}\n"
  },
  {
    "path": "pkg/ai/watsonxai.go",
    "content": "package ai\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\twx \"github.com/IBM/watsonx-go/pkg/models\"\n)\n\nconst ibmWatsonxAIClientName = \"ibmwatsonxai\"\n\ntype IBMWatsonxAIClient struct {\n\tnopCloser\n\n\tclient       *wx.Client\n\tmodel        string\n\ttemperature  float32\n\ttopP         float32\n\ttopK         int32\n\tmaxNewTokens int\n}\n\nconst (\n\tmodelMetallama = \"ibm/granite-13b-chat-v2\"\n\tmaxTokens      = 2048\n)\n\nfunc (c *IBMWatsonxAIClient) Configure(config IAIConfig) error {\n\tif config.GetModel() == \"\" {\n\t\tc.model = modelMetallama\n\t} else {\n\t\tc.model = config.GetModel()\n\t}\n\tif config.GetMaxTokens() == 0 {\n\t\tc.maxNewTokens = maxTokens\n\t} else {\n\t\tc.maxNewTokens = config.GetMaxTokens()\n\t}\n\tc.temperature = config.GetTemperature()\n\tc.topP = config.GetTopP()\n\tc.topK = config.GetTopK()\n\n\tapiKey := config.GetPassword()\n\tif apiKey == \"\" {\n\t\treturn errors.New(\"No watsonx API key provided\")\n\t}\n\n\tprojectId := config.GetProviderId()\n\tif projectId == \"\" {\n\t\treturn errors.New(\"No watsonx project ID provided\")\n\t}\n\n\tclient, err := wx.NewClient(\n\t\twx.WithWatsonxAPIKey(apiKey),\n\t\twx.WithWatsonxProjectID(projectId),\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to create client for testing. Error: %v\", err)\n\t}\n\tc.client = client\n\n\treturn nil\n}\n\nfunc (c *IBMWatsonxAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\tresult, err := c.client.GenerateText(\n\t\tc.model,\n\t\tprompt,\n\t\twx.WithTemperature((float64)(c.temperature)),\n\t\twx.WithTopP((float64)(c.topP)),\n\t\twx.WithTopK((uint)(c.topK)),\n\t\twx.WithMaxNewTokens((uint)(c.maxNewTokens)),\n\t)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"Expected no error, but got an error: %v\", err)\n\t}\n\tif result.Text == \"\" {\n\t\treturn \"\", errors.New(\"Expected a result, but got an empty string\")\n\t}\n\treturn result.Text, nil\n}\n\nfunc (c *IBMWatsonxAIClient) GetName() string {\n\treturn ibmWatsonxAIClientName\n}\n"
  },
  {
    "path": "pkg/analysis/analysis.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analysis\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\topenapi_v2 \"github.com/google/gnostic/openapiv2\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/custom\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/schollz/progressbar/v3\"\n\t\"github.com/spf13/viper\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype Analysis struct {\n\tContext            context.Context\n\tFilters            []string\n\tClient             *kubernetes.Client\n\tLanguage           string\n\tAIClient           ai.IAI\n\tResults            []common.Result\n\tErrors             []string\n\tNamespace          string\n\tLabelSelector      string\n\tCache              cache.ICache\n\tExplain            bool\n\tMaxConcurrency     int\n\tAnalysisAIProvider string // The name of the AI Provider used for this analysis\n\tWithDoc            bool\n\tWithStats          bool\n\tStats              []common.AnalysisStats\n}\n\ntype (\n\tAnalysisStatus string\n\tAnalysisErrors []string\n)\n\nconst (\n\tStateOK              AnalysisStatus = \"OK\"\n\tStateProblemDetected AnalysisStatus = \"ProblemDetected\"\n)\n\ntype JsonOutput struct {\n\tProvider string          `json:\"provider\"`\n\tErrors   AnalysisErrors  `json:\"errors\"`\n\tStatus   AnalysisStatus  `json:\"status\"`\n\tProblems int             `json:\"problems\"`\n\tResults  []common.Result `json:\"results\"`\n}\n\nfunc NewAnalysis(\n\tbackend string,\n\tlanguage string,\n\tfilters []string,\n\tnamespace string,\n\tlabelSelector string,\n\tnoCache bool,\n\texplain bool,\n\tmaxConcurrency int,\n\twithDoc bool,\n\tinteractiveMode bool,\n\thttpHeaders []string,\n\twithStats bool,\n) (*Analysis, error) {\n\t// Get kubernetes client from viper.\n\tkubecontext := viper.GetString(\"kubecontext\")\n\tkubeconfig := viper.GetString(\"kubeconfig\")\n\tverbose := viper.GetBool(\"verbose\")\n\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\tif verbose {\n\t\tfmt.Println(\"Debug: Checking kubernetes client initialization.\")\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"initialising kubernetes client: %w\", err)\n\t}\n\tif verbose {\n\t\tfmt.Printf(\"Debug: Kubernetes client initialized, server=%s.\\n\", client.Config.Host)\n\t}\n\n\t// Load remote cache if it is configured.\n\tcache, err := cache.GetCacheConfiguration()\n\tif verbose {\n\t\tfmt.Println(\"Debug: Checking cache configuration.\")\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif verbose {\n\t\tfmt.Printf(\"Debug: Cache configuration loaded, type=%s.\\n\", cache.GetName())\n\t}\n\n\tif noCache {\n\t\tcache.DisableCache()\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Cache disabled.\")\n\t\t}\n\t}\n\n\ta := &Analysis{\n\t\tContext:        context.Background(),\n\t\tFilters:        filters,\n\t\tClient:         client,\n\t\tLanguage:       language,\n\t\tNamespace:      namespace,\n\t\tLabelSelector:  labelSelector,\n\t\tCache:          cache,\n\t\tExplain:        explain,\n\t\tMaxConcurrency: maxConcurrency,\n\t\tWithDoc:        withDoc,\n\t\tWithStats:      withStats,\n\t}\n\tif verbose {\n\t\tfmt.Print(\"Debug: Analysis configuration loaded, \")\n\t\tfmt.Printf(\"filters=%v, language=%s, \", filters, language)\n\t\tif namespace == \"\" {\n\t\t\tfmt.Printf(\"namespace=none, \")\n\t\t} else {\n\t\t\tfmt.Printf(\"namespace=%s, \", namespace)\n\t\t}\n\t\tif labelSelector == \"\" {\n\t\t\tfmt.Printf(\"labelSelector=none, \")\n\t\t} else {\n\t\t\tfmt.Printf(\"labelSelector=%s, \", labelSelector)\n\t\t}\n\t\tfmt.Printf(\"explain=%t, maxConcurrency=%d, \", explain, maxConcurrency)\n\t\tfmt.Printf(\"withDoc=%t, withStats=%t.\\n\", withDoc, withStats)\n\t}\n\tif !explain {\n\t\t// Return early if AI use was not requested.\n\t\treturn a, nil\n\t}\n\n\tvar configAI ai.AIConfiguration\n\tif verbose {\n\t\tfmt.Println(\"Debug: Checking AI configuration.\")\n\t}\n\tif err := viper.UnmarshalKey(\"ai\", &configAI); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(configAI.Providers) == 0 {\n\t\treturn nil, errors.New(\"AI provider not specified in configuration. Please run k8sgpt auth\")\n\t}\n\n\t// Backend string will have high priority than a default provider\n\t// Hence, use the default provider only if the backend is not specified by the user.\n\tif configAI.DefaultProvider != \"\" && backend == \"\" {\n\t\tbackend = configAI.DefaultProvider\n\t\tif verbose {\n\t\t\tfmt.Printf(\"Debug: Using default AI provider %s.\\n\", backend)\n\t\t}\n\t}\n\n\tif backend == \"\" {\n\t\tbackend = \"openai\"\n\t\tif verbose {\n\t\t\tfmt.Printf(\"Debug: Using default AI provider %s.\\n\", backend)\n\t\t}\n\t}\n\n\tvar aiProvider ai.AIProvider\n\tfor _, provider := range configAI.Providers {\n\t\tif backend == provider.Name {\n\t\t\taiProvider = provider\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif aiProvider.Name == \"\" {\n\t\treturn nil, fmt.Errorf(\"AI provider %s not specified in configuration. Please run k8sgpt auth\", backend)\n\t}\n\n\tif verbose {\n\t\tfmt.Printf(\"Debug: AI configuration loaded, provider=%s, \", backend)\n\t\tfmt.Printf(\"baseUrl=%s, model=%s.\\n\", aiProvider.BaseURL, aiProvider.Model)\n\t}\n\n\taiClient := ai.NewClient(aiProvider.Name)\n\tcustomHeaders := util.NewHeaders(httpHeaders)\n\taiProvider.CustomHeaders = customHeaders\n\tif verbose {\n\t\tfmt.Println(\"Debug: Checking AI client initialization.\")\n\t}\n\tif err := aiClient.Configure(&aiProvider); err != nil {\n\t\treturn nil, err\n\t}\n\tif verbose {\n\t\tfmt.Println(\"Debug: AI client initialized.\")\n\t}\n\ta.AIClient = aiClient\n\ta.AnalysisAIProvider = aiProvider.Name\n\treturn a, nil\n}\n\nfunc (a *Analysis) CustomAnalyzersAreAvailable() bool {\n\tvar customAnalyzers []custom.CustomAnalyzer\n\tif err := viper.UnmarshalKey(\"custom_analyzers\", &customAnalyzers); err != nil {\n\t\treturn false\n\t}\n\treturn len(customAnalyzers) > 0\n}\n\nfunc (a *Analysis) RunCustomAnalysis() {\n\t// Validate namespace if specified, consistent with built-in filter behavior\n\tif a.Namespace != \"\" && a.Client != nil {\n\t\t_, err := a.Client.Client.CoreV1().Namespaces().Get(a.Context, a.Namespace, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"namespace %q not found: %s\", a.Namespace, err))\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar customAnalyzers []custom.CustomAnalyzer\n\tif err := viper.UnmarshalKey(\"custom_analyzers\", &customAnalyzers); err != nil {\n\t\ta.Errors = append(a.Errors, err.Error())\n\t\treturn\n\t}\n\n\tsemaphore := make(chan struct{}, a.MaxConcurrency)\n\tvar wg sync.WaitGroup\n\tvar mutex sync.Mutex\n\tverbose := viper.GetBool(\"verbose\")\n\tif verbose {\n\t\tif len(customAnalyzers) == 0 {\n\t\t\tfmt.Println(\"Debug: No custom analyzers found.\")\n\t\t} else {\n\t\t\tcAnalyzerNames := make([]string, len(customAnalyzers))\n\t\t\tfor i, cAnalyzer := range customAnalyzers {\n\t\t\t\tcAnalyzerNames[i] = cAnalyzer.Name\n\t\t\t}\n\t\t\tfmt.Printf(\"Debug: Found custom analyzers %v.\\n\", cAnalyzerNames)\n\t\t}\n\t}\n\tfor _, cAnalyzer := range customAnalyzers {\n\t\twg.Add(1)\n\t\tsemaphore <- struct{}{}\n\t\tgo func(analyzer custom.CustomAnalyzer, wg *sync.WaitGroup, semaphore chan struct{}) {\n\t\t\tdefer wg.Done()\n\t\t\tcanClient, err := custom.NewClient(cAnalyzer.Connection)\n\t\t\tif err != nil {\n\t\t\t\tmutex.Lock()\n\t\t\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"Client creation error for %s analyzer\", cAnalyzer.Name))\n\t\t\t\tmutex.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif verbose {\n\t\t\t\tfmt.Printf(\"Debug: %s launched.\\n\", cAnalyzer.Name)\n\t\t\t}\n\n\t\t\tresult, err := canClient.Run()\n\t\t\tif result.Kind == \"\" {\n\t\t\t\t// for custom analyzer name, we must use a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.',\n\t\t\t\t//and must start and end with an alphanumeric character (e.g. 'example.com',\n\t\t\t\t//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\t\tresult.Kind = cAnalyzer.Name\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tmutex.Lock()\n\t\t\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"[%s] %s\", cAnalyzer.Name, err))\n\t\t\t\tmutex.Unlock()\n\t\t\t\tif verbose {\n\t\t\t\t\tfmt.Printf(\"Debug: %s completed with errors.\\n\", cAnalyzer.Name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmutex.Lock()\n\t\t\t\ta.Results = append(a.Results, result)\n\t\t\t\tmutex.Unlock()\n\t\t\t\tif verbose {\n\t\t\t\t\tfmt.Printf(\"Debug: %s completed without errors.\\n\", cAnalyzer.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t\t<-semaphore\n\t\t}(cAnalyzer, &wg, semaphore)\n\t}\n\twg.Wait()\n}\n\nfunc (a *Analysis) RunAnalysis() {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\tverbose := viper.GetBool(\"verbose\")\n\n\tcoreAnalyzerMap, analyzerMap := analyzer.GetAnalyzerMap()\n\n\t// we get the openapi schema from the server only if required by the flag \"with-doc\"\n\topenapiSchema := &openapi_v2.Document{}\n\tif a.WithDoc {\n\t\tvar openApiErr error\n\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Fetching Kubernetes docs.\")\n\t\t}\n\t\topenapiSchema, openApiErr = a.Client.Client.Discovery().OpenAPISchema()\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: Checking Kubernetes docs.\")\n\t\t}\n\t\tif openApiErr != nil {\n\t\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"[KubernetesDoc] %s\", openApiErr))\n\t\t}\n\t}\n\n\tanalyzerConfig := common.Analyzer{\n\t\tClient:        a.Client,\n\t\tContext:       a.Context,\n\t\tNamespace:     a.Namespace,\n\t\tLabelSelector: a.LabelSelector,\n\t\tAIClient:      a.AIClient,\n\t\tOpenapiSchema: openapiSchema,\n\t}\n\n\t// Set a reasonable maximum for concurrency to prevent excessive memory allocation\n\tconst maxAllowedConcurrency = 100\n\tconcurrency := a.MaxConcurrency\n\tif concurrency <= 0 {\n\t\tconcurrency = 10 // Default value if not set\n\t} else if concurrency > maxAllowedConcurrency {\n\t\tconcurrency = maxAllowedConcurrency // Cap at a reasonable maximum\n\t}\n\n\tsemaphore := make(chan struct{}, concurrency)\n\tvar wg sync.WaitGroup\n\tvar mutex sync.Mutex\n\t// if there are no filters selected and no active_filters then run coreAnalyzer\n\tif len(a.Filters) == 0 && len(activeFilters) == 0 {\n\t\tif verbose {\n\t\t\tfmt.Println(\"Debug: No filters selected and no active filters found, run all core analyzers.\")\n\t\t}\n\t\tfor name, analyzer := range coreAnalyzerMap {\n\t\t\twg.Add(1)\n\t\t\tsemaphore <- struct{}{}\n\t\t\tgo a.executeAnalyzer(analyzer, name, analyzerConfig, semaphore, &wg, &mutex)\n\n\t\t}\n\t\twg.Wait()\n\t\treturn\n\t}\n\t// if the filters flag is specified\n\tif len(a.Filters) != 0 {\n\t\tif verbose {\n\t\t\tfmt.Printf(\"Debug: Filter flags %v specified, run selected core analyzers.\\n\", a.Filters)\n\t\t}\n\t\tfor _, filter := range a.Filters {\n\t\t\tif analyzer, ok := analyzerMap[filter]; ok {\n\t\t\t\tsemaphore <- struct{}{}\n\t\t\t\twg.Add(1)\n\t\t\t\tgo a.executeAnalyzer(analyzer, filter, analyzerConfig, semaphore, &wg, &mutex)\n\t\t\t} else {\n\t\t\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"\\\"%s\\\" filter does not exist. Please run k8sgpt filters list.\", filter))\n\t\t\t}\n\t\t}\n\t\twg.Wait()\n\t\treturn\n\t}\n\n\t// use active_filters\n\tif len(activeFilters) > 0 && verbose {\n\t\tfmt.Printf(\"Debug: Found active filters %v, run selected core analyzers.\\n\", activeFilters)\n\t}\n\tfor _, filter := range activeFilters {\n\t\tif analyzer, ok := analyzerMap[filter]; ok {\n\t\t\tsemaphore <- struct{}{}\n\t\t\twg.Add(1)\n\t\t\tgo a.executeAnalyzer(analyzer, filter, analyzerConfig, semaphore, &wg, &mutex)\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc (a *Analysis) executeAnalyzer(analyzer common.IAnalyzer, filter string, analyzerConfig common.Analyzer, semaphore chan struct{}, wg *sync.WaitGroup, mutex *sync.Mutex) {\n\tdefer wg.Done()\n\n\tvar startTime time.Time\n\tvar elapsedTime time.Duration\n\n\t// Start the timer\n\tif a.WithStats {\n\t\tstartTime = time.Now()\n\t}\n\n\t// Run the analyzer\n\tverbose := viper.GetBool(\"verbose\")\n\tif verbose {\n\t\tfmt.Printf(\"Debug: %s launched.\\n\", reflect.TypeOf(analyzer).Name())\n\t}\n\tresults, err := analyzer.Analyze(analyzerConfig)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\t// Measure the time taken\n\tif a.WithStats {\n\t\telapsedTime = time.Since(startTime)\n\t}\n\tstat := common.AnalysisStats{\n\t\tAnalyzer:     filter,\n\t\tDurationTime: elapsedTime,\n\t}\n\n\tmutex.Lock()\n\tdefer mutex.Unlock()\n\n\tif err != nil {\n\t\tif a.WithStats {\n\t\t\ta.Stats = append(a.Stats, stat)\n\t\t}\n\t\ta.Errors = append(a.Errors, fmt.Sprintf(\"[%s] %s\", filter, err))\n\t\tif verbose {\n\t\t\tfmt.Printf(\"Debug: %s completed with errors.\\n\", reflect.TypeOf(analyzer).Name())\n\t\t}\n\t} else {\n\t\tif a.WithStats {\n\t\t\ta.Stats = append(a.Stats, stat)\n\t\t}\n\t\ta.Results = append(a.Results, results...)\n\t\tif verbose {\n\t\t\tfmt.Printf(\"Debug: %s completed without errors.\\n\", reflect.TypeOf(analyzer).Name())\n\t\t}\n\t}\n\t<-semaphore\n}\n\nfunc (a *Analysis) GetAIResults(output string, anonymize bool) error {\n\tif len(a.Results) == 0 {\n\t\treturn nil\n\t}\n\n\tverbose := viper.GetBool(\"verbose\")\n\tif verbose {\n\t\tfmt.Println(\"Debug: Generating AI analysis.\")\n\t}\n\n\tvar bar *progressbar.ProgressBar\n\tif output != \"json\" {\n\t\tbar = progressbar.Default(int64(len(a.Results)))\n\t}\n\n\tfor index, analysis := range a.Results {\n\t\tvar texts []string\n\n\t\tif bar != nil && verbose {\n\t\t\tbar.Describe(fmt.Sprintf(\"Analyzing %s\", analysis.Kind))\n\t\t}\n\n\t\tfor _, failure := range analysis.Error {\n\t\t\tif anonymize {\n\t\t\t\tfor _, s := range failure.Sensitive {\n\t\t\t\t\tfailure.Text = util.ReplaceIfMatch(failure.Text, s.Unmasked, s.Masked)\n\t\t\t\t}\n\t\t\t}\n\t\t\ttexts = append(texts, failure.Text)\n\t\t}\n\n\t\tpromptTemplate := ai.PromptMap[\"default\"]\n\t\t// If the resource `Kind` comes from an \"integration plugin\",\n\t\t// maybe a customized prompt template will be involved.\n\t\tif prompt, ok := ai.PromptMap[analysis.Kind]; ok {\n\t\t\tpromptTemplate = prompt\n\t\t}\n\t\tresult, err := a.getAIResultForSanitizedFailures(texts, promptTemplate)\n\t\tif err != nil {\n\t\t\t// FIXME: can we avoid checking if output is json multiple times?\n\t\t\t//   maybe implement the progress bar better?\n\t\t\tif output != \"json\" {\n\t\t\t\t_ = bar.Exit()\n\t\t\t}\n\n\t\t\t// Check for exhaustion.\n\t\t\tif strings.Contains(err.Error(), \"status code: 429\") {\n\t\t\t\treturn fmt.Errorf(\"exhausted API quota for AI provider %s: %v\", a.AIClient.GetName(), err)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed while calling AI provider %s: %v\", a.AIClient.GetName(), err)\n\t\t}\n\n\t\tif anonymize {\n\t\t\tfor _, failure := range analysis.Error {\n\t\t\t\tfor _, s := range failure.Sensitive {\n\t\t\t\t\tresult = strings.ReplaceAll(result, s.Masked, s.Unmasked)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tanalysis.Details = result\n\t\tif output != \"json\" {\n\t\t\t_ = bar.Add(1)\n\t\t}\n\t\ta.Results[index] = analysis\n\t}\n\treturn nil\n}\n\nfunc (a *Analysis) getAIResultForSanitizedFailures(texts []string, promptTmpl string) (string, error) {\n\tinputKey := strings.Join(texts, \" \")\n\t// Check for cached data.\n\t// TODO(bwplotka): This might depend on model too (or even other client configuration pieces), fix it in later PRs.\n\tcacheKey := util.GetCacheKey(a.AIClient.GetName(), a.Language, inputKey)\n\n\tif !a.Cache.IsCacheDisabled() && a.Cache.Exists(cacheKey) {\n\t\tresponse, err := a.Cache.Load(cacheKey)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif response != \"\" {\n\t\t\toutput, err := base64.StdEncoding.DecodeString(response)\n\t\t\tif err == nil {\n\t\t\t\treturn string(output), nil\n\t\t\t}\n\t\t\tcolor.Red(\"error decoding cached data; ignoring cache item: %v\", err)\n\t\t}\n\t}\n\n\t// Process template.\n\tprompt := fmt.Sprintf(strings.TrimSpace(promptTmpl), a.Language, inputKey)\n\tif a.AIClient.GetName() == ai.CustomRestClientName {\n\t\t// Use proper JSON marshaling to handle special characters in error messages\n\t\t// This fixes issues with quotes, newlines, and other special chars in inputKey\n\t\tcustomRestPrompt := struct {\n\t\t\tLanguage string `json:\"language\"`\n\t\t\tMessage  string `json:\"message\"`\n\t\t\tPrompt   string `json:\"prompt\"`\n\t\t}{\n\t\t\tLanguage: a.Language,\n\t\t\tMessage:  inputKey,\n\t\t\tPrompt:   prompt,\n\t\t}\n\t\tpromptBytes, err := json.Marshal(customRestPrompt)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to marshal customrest prompt: %w\", err)\n\t\t}\n\t\tprompt = string(promptBytes)\n\t}\n\tresponse, err := a.AIClient.GetCompletion(a.Context, prompt)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif err = a.Cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))); err != nil {\n\t\tcolor.Red(\"error storing value to cache; value won't be cached: %v\", err)\n\t}\n\treturn response, nil\n}\n\nfunc (a *Analysis) Close() {\n\tif a.AIClient == nil {\n\t\treturn\n\t}\n\ta.AIClient.Close()\n}\n"
  },
  {
    "path": "pkg/analysis/analysis_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analysis\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/agiledragon/gomonkey/v2\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/magiconair/properties/assert\"\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n\tv1 \"k8s.io/api/core/v1\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n\t\"k8s.io/client-go/rest\"\n)\n\n// helper function: get type name of an analyzer\nfunc getTypeName(i interface{}) string {\n\treturn reflect.TypeOf(i).Name()\n}\n\n// helper function: run analysis with filter\nfunc analysis_RunAnalysisFilterTester(t *testing.T, filterFlag string) []common.Result {\n\tclientset := fake.NewSimpleClientset(\n\t\t&v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: v1.PodStatus{\n\t\t\t\tPhase: v1.PodPending,\n\t\t\t\tConditions: []v1.PodCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    v1.PodScheduled,\n\t\t\t\t\t\tReason:  \"Unschedulable\",\n\t\t\t\t\t\tMessage: \"0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&v1.Endpoints{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t},\n\t\t&v1.Service{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\"app\": \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&networkingv1.Ingress{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t},\n\t)\n\n\tanalysis := Analysis{\n\t\tContext:        context.Background(),\n\t\tResults:        []common.Result{},\n\t\tNamespace:      \"default\",\n\t\tMaxConcurrency: 1,\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tWithDoc: true,\n\t}\n\tif len(filterFlag) > 0 {\n\t\t// `--filter` is explicitly given\n\t\tanalysis.Filters = strings.Split(filterFlag, \",\")\n\t}\n\tanalysis.RunAnalysis()\n\treturn analysis.Results\n\n}\n\n// Test: Filter logic with running different Analyzers\nfunc TestAnalysis_RunAnalysisWithFilter(t *testing.T) {\n\tvar results []common.Result\n\tvar filterFlag string\n\n\t//1. Neither --filter flag Nor active filter is specified, only the \"core analyzers\"\n\tresults = analysis_RunAnalysisFilterTester(t, \"\")\n\tassert.Equal(t, len(results), 3) // all built-in resource will be analyzed\n\n\t//2. When the --filter flag is specified\n\n\tfilterFlag = \"Pod\" // --filter=Pod\n\tresults = analysis_RunAnalysisFilterTester(t, filterFlag)\n\tassert.Equal(t, len(results), 1)\n\tassert.Equal(t, results[0].Kind, filterFlag)\n\n\tfilterFlag = \"Ingress,Pod\" // --filter=Ingress,Pod\n\tresults = analysis_RunAnalysisFilterTester(t, filterFlag)\n\tassert.Equal(t, len(results), 2)\n}\n\n// Test:  Filter logic with Active Filter\nfunc TestAnalysis_RunAnalysisActiveFilter(t *testing.T) {\n\n\t//When the --filter flag is not specified but has actived filter in config\n\tvar results []common.Result\n\n\tviper.SetDefault(\"active_filters\", \"Ingress\")\n\tresults = analysis_RunAnalysisFilterTester(t, \"\")\n\tassert.Equal(t, len(results), 1)\n\n\tviper.SetDefault(\"active_filters\", []string{\"Ingress\", \"Service\"})\n\tresults = analysis_RunAnalysisFilterTester(t, \"\")\n\tassert.Equal(t, len(results), 2)\n\n\tviper.SetDefault(\"active_filters\", []string{\"Ingress\", \"Service\", \"Pod\"})\n\tresults = analysis_RunAnalysisFilterTester(t, \"\")\n\tassert.Equal(t, len(results), 3)\n\n\t// Invalid filter\n\tresults = analysis_RunAnalysisFilterTester(t, \"invalid\")\n\tassert.Equal(t, len(results), 0)\n}\n\nfunc TestAnalysis_NoProblemJsonOutput(t *testing.T) {\n\n\tanalysis := Analysis{\n\t\tResults:   []common.Result{},\n\t\tNamespace: \"default\",\n\t}\n\n\texpected := JsonOutput{\n\t\tStatus:   StateOK,\n\t\tProblems: 0,\n\t\tResults:  []common.Result{},\n\t}\n\n\tgotJson, err := analysis.PrintOutput(\"json\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tgot := JsonOutput{}\n\terr = json.Unmarshal(gotJson, &got)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfmt.Println(got)\n\tfmt.Println(expected)\n\n\trequire.Equal(t, got, expected)\n\n}\n\nfunc TestAnalysis_ProblemJsonOutput(t *testing.T) {\n\tanalysis := Analysis{\n\t\tResults: []common.Result{\n\t\t\t{\n\t\t\t\tKind: \"Deployment\",\n\t\t\t\tName: \"test-deployment\",\n\t\t\t\tError: []common.Failure{\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDetails:      \"test-solution\",\n\t\t\t\tParentObject: \"parent-resource\"},\n\t\t},\n\t\tNamespace: \"default\",\n\t}\n\n\texpected := JsonOutput{\n\t\tStatus:   StateProblemDetected,\n\t\tProblems: 1,\n\t\tResults: []common.Result{\n\t\t\t{\n\t\t\t\tKind: \"Deployment\",\n\t\t\t\tName: \"test-deployment\",\n\t\t\t\tError: []common.Failure{\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDetails:      \"test-solution\",\n\t\t\t\tParentObject: \"parent-resource\"},\n\t\t},\n\t}\n\n\tgotJson, err := analysis.PrintOutput(\"json\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tgot := JsonOutput{}\n\terr = json.Unmarshal(gotJson, &got)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfmt.Println(got)\n\tfmt.Println(expected)\n\n\trequire.Equal(t, got, expected)\n}\n\nfunc TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {\n\tanalysis := Analysis{\n\t\tResults: []common.Result{\n\t\t\t{\n\t\t\t\tKind: \"Deployment\",\n\t\t\t\tName: \"test-deployment\",\n\t\t\t\tError: []common.Failure{\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"another-test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDetails:      \"test-solution\",\n\t\t\t\tParentObject: \"parent-resource\"},\n\t\t},\n\t\tNamespace: \"default\",\n\t}\n\n\texpected := JsonOutput{\n\t\tStatus:   StateProblemDetected,\n\t\tProblems: 2,\n\t\tResults: []common.Result{\n\t\t\t{\n\t\t\t\tKind: \"Deployment\",\n\t\t\t\tName: \"test-deployment\",\n\t\t\t\tError: []common.Failure{\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tText:      \"another-test-problem\",\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tDetails:      \"test-solution\",\n\t\t\t\tParentObject: \"parent-resource\"},\n\t\t},\n\t}\n\n\tgotJson, err := analysis.PrintOutput(\"json\")\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tgot := JsonOutput{}\n\terr = json.Unmarshal(gotJson, &got)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfmt.Println(got)\n\tfmt.Println(expected)\n\n\trequire.Equal(t, got, expected)\n}\n\nfunc TestNewAnalysis(t *testing.T) {\n\tdisabledCache := cache.New(\"disabled-cache\")\n\tdisabledCache.DisableCache()\n\taiClient := &ai.NoOpAIClient{}\n\tresults := []common.Result{\n\t\t{\n\t\t\tKind: \"VulnerabilityReport\",\n\t\t\tError: []common.Failure{\n\t\t\t\t{\n\t\t\t\t\tText:          \"This is a custom failure\",\n\t\t\t\t\tKubernetesDoc: \"test-kubernetes-doc\",\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMasked:   \"masked-error\",\n\t\t\t\t\t\t\tUnmasked: \"unmasked-error\",\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\ttests := []struct {\n\t\tname        string\n\t\ta           Analysis\n\t\toutput      string\n\t\tanonymize   bool\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"Empty results\",\n\t\t\ta:    Analysis{},\n\t\t},\n\t\t{\n\t\t\tname: \"cache disabled\",\n\t\t\ta: Analysis{\n\t\t\t\tAIClient: aiClient,\n\t\t\t\tCache:    disabledCache,\n\t\t\t\tResults:  results,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"output and anonymize both set\",\n\t\t\ta: Analysis{\n\t\t\t\tAIClient: aiClient,\n\t\t\t\tCache:    cache.New(\"test-cache\"),\n\t\t\t\tResults:  results,\n\t\t\t},\n\t\t\toutput:    \"test-output\",\n\t\t\tanonymize: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := tt.a.GetAIResults(tt.output, tt.anonymize)\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetAIResultForSanitizedFailures(t *testing.T) {\n\tenabledCache := cache.New(\"enabled-cache\")\n\tdisabledCache := cache.New(\"disabled-cache\")\n\tdisabledCache.DisableCache()\n\taiClient := &ai.NoOpAIClient{}\n\n\ttests := []struct {\n\t\tname           string\n\t\ta              Analysis\n\t\ttexts          []string\n\t\tpromptTmpl     string\n\t\texpectedOutput string\n\t\texpectedErr    string\n\t}{\n\t\t{\n\t\t\tname: \"Cache enabled\",\n\t\t\ta: Analysis{\n\t\t\t\tAIClient: aiClient,\n\t\t\t\tCache:    enabledCache,\n\t\t\t},\n\t\t\ttexts:          []string{\"some-data\"},\n\t\t\texpectedOutput: \"I am a noop response to the prompt %!(EXTRA string=, string=some-data)\",\n\t\t},\n\t\t{\n\t\t\tname: \"cache disabled\",\n\t\t\ta: Analysis{\n\t\t\t\tAIClient: aiClient,\n\t\t\t\tCache:    disabledCache,\n\t\t\t\tLanguage: \"English\",\n\t\t\t},\n\t\t\ttexts:          []string{\"test input\"},\n\t\t\tpromptTmpl:     \"Response in %s: %s\",\n\t\t\texpectedOutput: \"I am a noop response to the prompt Response in English: test input\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\toutput, err := tt.a.getAIResultForSanitizedFailures(tt.texts, tt.promptTmpl)\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, tt.expectedOutput, output)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t\trequire.Empty(t, output)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test: Verbose output in NewAnalysis with explain=false\nfunc TestVerbose_NewAnalysisWithoutExplain(t *testing.T) {\n\t// Set viper config.\n\tviper.Set(\"verbose\", true)\n\tviper.Set(\"kubecontext\", \"dummy\")\n\tviper.Set(\"kubeconfig\", \"dummy\")\n\n\t// Patch kubernetes.NewClient to return a dummy client.\n\tpatches := gomonkey.ApplyFunc(kubernetes.NewClient, func(kubecontext, kubeconfig string) (*kubernetes.Client, error) {\n\t\treturn &kubernetes.Client{\n\t\t\tConfig: &rest.Config{Host: \"fake-server\"},\n\t\t}, nil\n\t})\n\tdefer patches.Reset()\n\n\toutput := util.CaptureOutput(func() {\n\t\ta, err := NewAnalysis(\n\t\t\t\"\", \"english\", []string{\"Pod\"}, \"default\", \"\", true,\n\t\t\tfalse, // explain\n\t\t\t10, false, false, []string{}, false,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\ta.Close()\n\t})\n\n\texpectedOutputs := []string{\n\t\t\"Debug: Checking kubernetes client initialization.\",\n\t\t\"Debug: Kubernetes client initialized, server=fake-server.\",\n\t\t\"Debug: Checking cache configuration.\",\n\t\t\"Debug: Cache configuration loaded, type=file.\",\n\t\t\"Debug: Cache disabled.\",\n\t\t\"Debug: Analysis configuration loaded, filters=[Pod], language=english, namespace=default, labelSelector=none, explain=false, maxConcurrency=10, withDoc=false, withStats=false.\",\n\t}\n\tfor _, expected := range expectedOutputs {\n\t\tif !util.Contains(output, expected) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in NewAnalysis with explain=true\nfunc TestVerbose_NewAnalysisWithExplain(t *testing.T) {\n\t// Set viper config.\n\tviper.Set(\"verbose\", true)\n\tviper.Set(\"kubecontext\", \"dummy\")\n\tviper.Set(\"kubeconfig\", \"dummy\")\n\t// Set a dummy AI configuration.\n\tdummyAIConfig := map[string]interface{}{\n\t\t\"defaultProvider\": \"dummy\",\n\t\t\"providers\": []map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"name\":          \"dummy\",\n\t\t\t\t\"baseUrl\":       \"http://dummy\",\n\t\t\t\t\"model\":         \"dummy-model\",\n\t\t\t\t\"customHeaders\": map[string]string{},\n\t\t\t},\n\t\t},\n\t}\n\tviper.Set(\"ai\", dummyAIConfig)\n\n\t// Patch kubernetes.NewClient to return a dummy client.\n\tpatches := gomonkey.ApplyFunc(kubernetes.NewClient, func(kubecontext, kubeconfig string) (*kubernetes.Client, error) {\n\t\treturn &kubernetes.Client{\n\t\t\tConfig: &rest.Config{Host: \"fake-server\"},\n\t\t}, nil\n\t})\n\tdefer patches.Reset()\n\n\t// Patch ai.NewClient to return a NoOp client.\n\tpatches2 := gomonkey.ApplyFunc(ai.NewClient, func(name string) ai.IAI {\n\t\treturn &ai.NoOpAIClient{}\n\t})\n\tdefer patches2.Reset()\n\n\toutput := util.CaptureOutput(func() {\n\t\ta, err := NewAnalysis(\n\t\t\t\"\", \"english\", []string{\"Pod\"}, \"default\", \"\", true,\n\t\t\ttrue, // explain\n\t\t\t10, false, false, []string{}, false,\n\t\t)\n\t\trequire.NoError(t, err)\n\t\ta.Close()\n\t})\n\n\texpectedOutputs := []string{\n\t\t\"Debug: Checking AI configuration.\",\n\t\t\"Debug: Using default AI provider dummy.\",\n\t\t\"Debug: AI configuration loaded, provider=dummy, baseUrl=http://dummy, model=dummy-model.\",\n\t\t\"Debug: Checking AI client initialization.\",\n\t\t\"Debug: AI client initialized.\",\n\t}\n\tfor _, expected := range expectedOutputs {\n\t\tif !util.Contains(output, expected) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in RunAnalysis with filter flag\nfunc TestVerbose_RunAnalysisWithFilter(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\t// Run analysis with a filter flag (\"Pod\") to trigger debug output.\n\toutput := util.CaptureOutput(func() {\n\t\t_ = analysis_RunAnalysisFilterTester(t, \"Pod\")\n\t})\n\n\texpectedOutputs := []string{\n\t\t\"Debug: Filter flags [Pod] specified, run selected core analyzers.\",\n\t\t\"Debug: PodAnalyzer launched.\",\n\t\t\"Debug: PodAnalyzer completed without errors.\",\n\t}\n\n\tfor _, expected := range expectedOutputs {\n\t\tif !util.Contains(output, expected) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in RunAnalysis with active filter\nfunc TestVerbose_RunAnalysisWithActiveFilter(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\tviper.SetDefault(\"active_filters\", \"Ingress\")\n\toutput := util.CaptureOutput(func() {\n\t\t_ = analysis_RunAnalysisFilterTester(t, \"\")\n\t})\n\n\texpectedOutputs := []string{\n\t\t\"Debug: Found active filters [Ingress], run selected core analyzers.\",\n\t\t\"Debug: IngressAnalyzer launched.\",\n\t\t\"Debug: IngressAnalyzer completed without errors.\",\n\t}\n\n\tfor _, expected := range expectedOutputs {\n\t\tif !util.Contains(output, expected) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in RunAnalysis without any filter (run all core analyzers)\nfunc TestVerbose_RunAnalysisWithoutFilter(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\t// Clear filter flag and active_filters to run all core analyzers.\n\tviper.SetDefault(\"active_filters\", []string{})\n\toutput := util.CaptureOutput(func() {\n\t\t_ = analysis_RunAnalysisFilterTester(t, \"\")\n\t})\n\n\t// Check for debug message indicating no filters.\n\texpectedNoFilter := \"Debug: No filters selected and no active filters found, run all core analyzers.\"\n\tif !util.Contains(output, expectedNoFilter) {\n\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expectedNoFilter, output)\n\t}\n\n\t// Get all core analyzers from analyzer.GetAnalyzerMap()\n\tcoreAnalyzerMap, _ := analyzer.GetAnalyzerMap()\n\tfor _, analyzerInstance := range coreAnalyzerMap {\n\t\tanalyzerType := getTypeName(analyzerInstance)\n\t\texpectedLaunched := fmt.Sprintf(\"Debug: %s launched.\", analyzerType)\n\t\texpectedCompleted := fmt.Sprintf(\"Debug: %s completed without errors.\", analyzerType)\n\t\tif !util.Contains(output, expectedLaunched) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expectedLaunched, output)\n\t\t}\n\t\tif !util.Contains(output, expectedCompleted) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expectedCompleted, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in RunCustomAnalysis without custom analyzer\nfunc TestVerbose_RunCustomAnalysisWithoutCustomAnalyzer(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\t// Set custom_analyzers to empty array to trigger \"No custom analyzers\" debug message.\n\tviper.Set(\"custom_analyzers\", []interface{}{})\n\tanalysisObj := &Analysis{\n\t\tMaxConcurrency: 1,\n\t}\n\toutput := util.CaptureOutput(func() {\n\t\tanalysisObj.RunCustomAnalysis()\n\t})\n\texpected := \"Debug: No custom analyzers found.\"\n\tif !util.Contains(output, \"Debug: No custom analyzers found.\") {\n\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t}\n}\n\n// Test: Verbose output in RunCustomAnalysis with custom analyzer\nfunc TestVerbose_RunCustomAnalysisWithCustomAnalyzer(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\t// Set custom_analyzers with one custom analyzer using \"fake\" connection.\n\tviper.Set(\"custom_analyzers\", []map[string]interface{}{\n\t\t{\n\t\t\t\"name\":       \"TestCustomAnalyzer\",\n\t\t\t\"connection\": map[string]interface{}{\"url\": \"127.0.0.1\", \"port\": \"2333\"},\n\t\t},\n\t})\n\n\tanalysisObj := &Analysis{\n\t\tMaxConcurrency: 1,\n\t}\n\toutput := util.CaptureOutput(func() {\n\t\tanalysisObj.RunCustomAnalysis()\n\t})\n\tassert.Equal(t, 1, len(analysisObj.Errors)) // connection error\n\n\texpectedOutputs := []string{\n\t\t\"Debug: Found custom analyzers [TestCustomAnalyzer].\",\n\t\t\"Debug: TestCustomAnalyzer launched.\",\n\t\t\"Debug: TestCustomAnalyzer completed with errors.\",\n\t}\n\n\tfor _, expected := range expectedOutputs {\n\t\tif !util.Contains(output, expected) {\n\t\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t\t}\n\t}\n}\n\n// Test: Verbose output in GetAIResults\nfunc TestVerbose_GetAIResults(t *testing.T) {\n\tviper.Set(\"verbose\", true)\n\tdisabledCache := cache.New(\"disabled-cache\")\n\tdisabledCache.DisableCache()\n\taiClient := &ai.NoOpAIClient{}\n\tanalysisObj := Analysis{\n\t\tAIClient: aiClient,\n\t\tCache:    disabledCache,\n\t\tResults: []common.Result{\n\t\t\t{\n\t\t\t\tKind:         \"Deployment\",\n\t\t\t\tName:         \"test-deployment\",\n\t\t\t\tError:        []common.Failure{{Text: \"test-problem\", Sensitive: []common.Sensitive{}}},\n\t\t\t\tDetails:      \"test-solution\",\n\t\t\t\tParentObject: \"parent-resource\",\n\t\t\t},\n\t\t},\n\t\tNamespace: \"default\",\n\t}\n\toutput := util.CaptureOutput(func() {\n\t\t_ = analysisObj.GetAIResults(\"json\", false)\n\t})\n\n\texpected := \"Debug: Generating AI analysis.\"\n\tif !util.Contains(output, expected) {\n\t\tt.Errorf(\"Expected output to contain: '%s', but got output: '%s'\", expected, output)\n\t}\n}\n"
  },
  {
    "path": "pkg/analysis/output.go",
    "content": "package analysis\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n)\n\nvar outputFormats = map[string]func(*Analysis) ([]byte, error){\n\t\"json\": (*Analysis).jsonOutput,\n\t\"text\": (*Analysis).textOutput,\n}\n\nfunc getOutputFormats() []string {\n\tformats := make([]string, 0, len(outputFormats))\n\tfor format := range outputFormats {\n\t\tformats = append(formats, format)\n\t}\n\treturn formats\n}\n\nfunc (a *Analysis) PrintOutput(format string) ([]byte, error) {\n\toutputFunc, ok := outputFormats[format]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported output format: %s. Available format %s\", format, strings.Join(getOutputFormats(), \",\"))\n\t}\n\treturn outputFunc(a)\n}\n\nfunc (a *Analysis) jsonOutput() ([]byte, error) {\n\tvar problems int\n\tvar status AnalysisStatus\n\tfor _, result := range a.Results {\n\t\tproblems += len(result.Error)\n\t}\n\tif problems > 0 {\n\t\tstatus = StateProblemDetected\n\t} else {\n\t\tstatus = StateOK\n\t}\n\n\tresult := JsonOutput{\n\t\tProvider: a.AnalysisAIProvider,\n\t\tProblems: problems,\n\t\tResults:  a.Results,\n\t\tErrors:   a.Errors,\n\t\tStatus:   status,\n\t}\n\toutput, err := json.MarshalIndent(result, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshalling json: %v\", err)\n\t}\n\treturn output, nil\n}\n\nfunc (a *Analysis) PrintStats() []byte {\n\tvar output strings.Builder\n\n\toutput.WriteString(color.YellowString(\"The stats mode allows for debugging and understanding the time taken by an analysis by displaying the statistics of each analyzer.\\n\"))\n\n\tfor _, stat := range a.Stats {\n\t\toutput.WriteString(fmt.Sprintf(\"- Analyzer %s took %s \\n\", color.YellowString(stat.Analyzer), stat.DurationTime))\n\t}\n\n\treturn []byte(output.String())\n}\n\nfunc (a *Analysis) textOutput() ([]byte, error) {\n\tvar output strings.Builder\n\n\t// Print the AI provider used for this analysis (if explain was enabled).\n\tif a.Explain {\n\t\toutput.WriteString(fmt.Sprintf(\"AI Provider: %s\\n\", color.YellowString(a.AnalysisAIProvider)))\n\t} else {\n\t\toutput.WriteString(fmt.Sprintf(\"AI Provider: %s\\n\", color.YellowString(\"AI not used; --explain not set\")))\n\t}\n\n\tif len(a.Errors) != 0 {\n\t\toutput.WriteString(\"\\n\")\n\t\toutput.WriteString(color.YellowString(\"Warnings : \\n\"))\n\t\tfor _, aerror := range a.Errors {\n\t\t\toutput.WriteString(fmt.Sprintf(\"- %s\\n\", color.YellowString(aerror)))\n\t\t}\n\t}\n\toutput.WriteString(\"\\n\")\n\tif len(a.Results) == 0 {\n\t\toutput.WriteString(color.GreenString(\"No problems detected\\n\"))\n\t\treturn []byte(output.String()), nil\n\t}\n\tfor n, result := range a.Results {\n\t\toutput.WriteString(fmt.Sprintf(\"%s: %s %s(%s)\\n\", color.CyanString(\"%d\", n),\n\t\t\tcolor.HiYellowString(result.Kind),\n\t\t\tcolor.YellowString(result.Name),\n\t\t\tcolor.CyanString(result.ParentObject)))\n\t\tfor _, err := range result.Error {\n\t\t\toutput.WriteString(fmt.Sprintf(\"- %s %s\\n\", color.RedString(\"Error:\"), color.RedString(err.Text)))\n\t\t\tif err.KubernetesDoc != \"\" {\n\t\t\t\toutput.WriteString(fmt.Sprintf(\"  %s %s\\n\", color.RedString(\"Kubernetes Doc:\"), color.RedString(err.KubernetesDoc)))\n\t\t\t}\n\t\t}\n\t\toutput.WriteString(color.GreenString(result.Details + \"\\n\"))\n\t}\n\treturn []byte(output.String()), nil\n}\n"
  },
  {
    "path": "pkg/analysis/output_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analysis\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPrintOutput(t *testing.T) {\n\trequire.NotEmpty(t, getOutputFormats())\n\n\ttests := []struct {\n\t\tname           string\n\t\ta              *Analysis\n\t\tformat         string\n\t\texpectedOutput string\n\t\texpectedErr    string\n\t}{\n\t\t{\n\t\t\tname:           \"json format\",\n\t\t\ta:              &Analysis{},\n\t\t\tformat:         \"json\",\n\t\t\texpectedOutput: \"{\\n  \\\"provider\\\": \\\"\\\",\\n  \\\"errors\\\": null,\\n  \\\"status\\\": \\\"OK\\\",\\n  \\\"problems\\\": 0,\\n  \\\"results\\\": null\\n}\",\n\t\t},\n\t\t{\n\t\t\tname:           \"text format\",\n\t\t\ta:              &Analysis{},\n\t\t\tformat:         \"text\",\n\t\t\texpectedOutput: \"AI Provider: AI not used; --explain not set\\n\\nNo problems detected\\n\",\n\t\t},\n\t\t{\n\t\t\tname:        \"unsupported format\",\n\t\t\ta:           &Analysis{},\n\t\t\tformat:      \"unsupported\",\n\t\t\texpectedErr: \"unsupported output format: unsupported. Available format\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\toutput, err := tt.a.PrintOutput(tt.format)\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Contains(t, string(output), tt.expectedOutput)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t\trequire.Nil(t, output)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzer.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\tAnalyzerErrorsMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tName: \"analyzer_errors\",\n\t\tHelp: \"Number of errors detected by analyzer\",\n\t}, []string{\"analyzer_name\", \"object_name\", \"namespace\"})\n)\n\nvar coreAnalyzerMap = map[string]common.IAnalyzer{\n\t\"Pod\":                            PodAnalyzer{},\n\t\"Deployment\":                     DeploymentAnalyzer{},\n\t\"ReplicaSet\":                     ReplicaSetAnalyzer{},\n\t\"PersistentVolumeClaim\":          PvcAnalyzer{},\n\t\"Service\":                        ServiceAnalyzer{},\n\t\"Ingress\":                        IngressAnalyzer{},\n\t\"StatefulSet\":                    StatefulSetAnalyzer{},\n\t\"Job\":                            JobAnalyzer{},\n\t\"CronJob\":                        CronJobAnalyzer{},\n\t\"Node\":                           NodeAnalyzer{},\n\t\"ValidatingWebhookConfiguration\": ValidatingWebhookAnalyzer{},\n\t\"MutatingWebhookConfiguration\":   MutatingWebhookAnalyzer{},\n\t\"ConfigMap\":                      ConfigMapAnalyzer{},\n}\n\nvar additionalAnalyzerMap = map[string]common.IAnalyzer{\n\t\"HorizontalPodAutoscaler\": HpaAnalyzer{},\n\t\"PodDisruptionBudget\":     PdbAnalyzer{},\n\t\"NetworkPolicy\":           NetworkPolicyAnalyzer{},\n\t\"Log\":                     LogAnalyzer{},\n\t\"GatewayClass\":            GatewayClassAnalyzer{},\n\t\"Gateway\":                 GatewayAnalyzer{},\n\t\"HTTPRoute\":               HTTPRouteAnalyzer{},\n\t\"Storage\":                 StorageAnalyzer{},\n\t\"Security\":                SecurityAnalyzer{},\n\t\"ClusterCatalog\":          ClusterCatalogAnalyzer{},\n\t\"ClusterExtension\":        ClusterExtensionAnalyzer{},\n\t\"ClusterServiceVersion\":   ClusterServiceVersionAnalyzer{},\n\t\"Subscription\":            SubscriptionAnalyzer{},\n\t\"InstallPlan\":             InstallPlanAnalyzer{},\n\t\"CatalogSource\":           CatalogSourceAnalyzer{},\n\t\"OperatorGroup\":           OperatorGroupAnalyzer{},\n}\n\nfunc ListFilters() ([]string, []string, []string) {\n\tcoreKeys := make([]string, 0, len(coreAnalyzerMap))\n\tfor k := range coreAnalyzerMap {\n\t\tcoreKeys = append(coreKeys, k)\n\t}\n\n\tadditionalKeys := make([]string, 0, len(additionalAnalyzerMap))\n\tfor k := range additionalAnalyzerMap {\n\t\tadditionalKeys = append(additionalKeys, k)\n\t}\n\n\tintegrationProvider := integration.NewIntegration()\n\tvar integrationAnalyzers []string\n\n\tfor _, i := range integrationProvider.List() {\n\t\tb, _ := integrationProvider.IsActivate(i)\n\t\tif b {\n\t\t\tin, err := integrationProvider.Get(i)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(color.RedString(err.Error()))\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tintegrationAnalyzers = append(integrationAnalyzers, in.GetAnalyzerName()...)\n\t\t}\n\t}\n\n\treturn coreKeys, additionalKeys, integrationAnalyzers\n}\n\nfunc GetAnalyzerMap() (map[string]common.IAnalyzer, map[string]common.IAnalyzer) {\n\n\tcoreAnalyzer := make(map[string]common.IAnalyzer)\n\tmergedAnalyzerMap := make(map[string]common.IAnalyzer)\n\n\t// add core analyzer\n\tfor key, value := range coreAnalyzerMap {\n\t\tcoreAnalyzer[key] = value\n\t\tmergedAnalyzerMap[key] = value\n\t}\n\n\t// add additional analyzer\n\tfor key, value := range additionalAnalyzerMap {\n\t\tmergedAnalyzerMap[key] = value\n\t}\n\n\tintegrationProvider := integration.NewIntegration()\n\n\tfor _, i := range integrationProvider.List() {\n\t\tb, err := integrationProvider.IsActivate(i)\n\t\tif err != nil {\n\t\t\tfmt.Println(color.RedString(err.Error()))\n\t\t\tos.Exit(1)\n\t\t}\n\t\tif b {\n\t\t\tin, err := integrationProvider.Get(i)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(color.RedString(err.Error()))\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tin.AddAnalyzer(&mergedAnalyzerMap)\n\t\t}\n\t}\n\n\treturn coreAnalyzer, mergedAnalyzerMap\n}\n"
  },
  {
    "path": "pkg/analyzer/catalogsource.go",
    "content": "package analyzer\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\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\ntype CatalogSourceAnalyzer struct{}\n\nvar catSrcGVR = schema.GroupVersionResource{\n\tGroup:    \"operators.coreos.com\",\n\tVersion:  \"v1alpha1\",\n\tResource: \"catalogsources\",\n}\n\nfunc (CatalogSourceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"CatalogSource\"\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in %s analyzer\", kind)\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().\n\t\tResource(catSrcGVR).Namespace(metav1.NamespaceAll).\n\t\tList(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar results []common.Result\n\tfor _, item := range list.Items {\n\t\tns, name := item.GetNamespace(), item.GetName()\n\n\t\tstate, _, _ := unstructured.NestedString(item.Object, \"status\", \"connectionState\", \"lastObservedState\")\n\t\taddr, _, _ := unstructured.NestedString(item.Object, \"status\", \"connectionState\", \"address\")\n\n\t\t// Only report if state is present and not READY\n\t\tif state != \"\" && strings.ToUpper(state) != \"READY\" {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind: kind,\n\t\t\t\tName: ns + \"/\" + name,\n\t\t\t\tError: []common.Failure{{\n\t\t\t\t\tText: fmt.Sprintf(\"connectionState=%s (address=%s)\", state, addr),\n\t\t\t\t}},\n\t\t\t})\n\t\t}\n\t}\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/catalogsource_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n)\n\nfunc TestCatalogSourceAnalyzer_UnhealthyState_ReturnsResult(t *testing.T) {\n\tcs := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"CatalogSource\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"broken-operators-external\",\n\t\t\t\t\"namespace\": \"openshift-marketplace\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"connectionState\": map[string]any{\n\t\t\t\t\t\"lastObservedState\": \"TRANSIENT_FAILURE\",\n\t\t\t\t\t\"address\":           \"not-a-real-host.invalid:50051\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"catalogsources\"}: \"CatalogSourceList\",\n\t}\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, cs)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (CatalogSourceAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\tif len(res) != 1 {\n\t\tt.Fatalf(\"expected 1 result, got %d\", len(res))\n\t}\n\tif res[0].Kind != \"CatalogSource\" || !strings.Contains(res[0].Name, \"openshift-marketplace/broken-operators-external\") {\n\t\tt.Fatalf(\"unexpected result: %#v\", res[0])\n\t}\n\tif len(res[0].Error) == 0 || !strings.Contains(res[0].Error[0].Text, \"TRANSIENT_FAILURE\") {\n\t\tt.Fatalf(\"expected TRANSIENT_FAILURE in message, got %#v\", res[0].Error)\n\t}\n}\n\nfunc TestCatalogSourceAnalyzer_HealthyOrNoState_Ignored(t *testing.T) {\n\t// One READY (healthy), one with no status at all: both should be ignored.\n\tready := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"CatalogSource\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"ready-operators\",\n\t\t\t\t\"namespace\": \"openshift-marketplace\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"connectionState\": map[string]any{\n\t\t\t\t\t\"lastObservedState\": \"READY\",\n\t\t\t\t\t\"address\":           \"somewhere\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tnostate := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"CatalogSource\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"no-status-operators\",\n\t\t\t\t\"namespace\": \"openshift-marketplace\",\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"catalogsources\"}: \"CatalogSourceList\",\n\t}\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, ready, nostate)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (CatalogSourceAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\tif len(res) != 0 {\n\t\tt.Fatalf(\"expected 0 results (healthy/nostate ignored), got %d\", len(res))\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/clustercatalog.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\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/apimachinery/pkg/runtime/schema\"\n)\n\ntype ClusterCatalogAnalyzer struct{}\n\nfunc (ClusterCatalogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"ClusterCatalog\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tvar clusterCatalogGVR = schema.GroupVersionResource{\n\t\tGroup:    \"olm.operatorframework.io\",\n\t\tVersion:  \"v1\",\n\t\tResource: \"clustercatalogs\",\n\t}\n\tif a.Client == nil {\n\t\treturn nil, fmt.Errorf(\"client is nil in ClusterCatalogAnalyzer\")\n\t}\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in ClusterCatalogAnalyzer\")\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().Resource(clusterCatalogGVR).Namespace(\"\").List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, item := range list.Items {\n\t\tvar failures []common.Failure\n\t\tcatalog, err := ConvertToClusterCatalog(&item)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"ClusterCatalog: %s | Source: %s\\n\", catalog.Name, catalog.Spec.Source.Image.Ref)\n\t\tfailures, err = ValidateClusterCatalog(failures, catalog)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[catalog.Name] = common.PreAnalysis{\n\t\t\t\tCatalog:        *catalog,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, catalog.Name, \"\").Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Node.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, err\n}\n\nfunc ConvertToClusterCatalog(u *unstructured.Unstructured) (*common.ClusterCatalog, error) {\n\tvar cc common.ClusterCatalog\n\terr := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert to ClusterCatalog: %w\", err)\n\t}\n\treturn &cc, nil\n}\n\nfunc addCatalogConditionFailure(failures []common.Failure, catalogName string, catalogCondition metav1.Condition) []common.Failure {\n\tfailures = append(failures, common.Failure{\n\t\tText: fmt.Sprintf(\"OLMv1 ClusterCatalog: %s has condition of type %s, reason %s: %s\", catalogName, catalogCondition.Type, catalogCondition.Reason, catalogCondition.Message),\n\t\tSensitive: []common.Sensitive{\n\t\t\t{\n\t\t\t\tUnmasked: catalogName,\n\t\t\t\tMasked:   util.MaskString(catalogName),\n\t\t\t},\n\t\t},\n\t})\n\treturn failures\n}\n\nfunc addCatalogFailure(failures []common.Failure, catalogName string, err error) []common.Failure {\n\tfailures = append(failures, common.Failure{\n\t\tText: fmt.Sprintf(\"%s has error: %s\", catalogName, err.Error()),\n\t\tSensitive: []common.Sensitive{\n\t\t\t{\n\t\t\t\tUnmasked: catalogName,\n\t\t\t\tMasked:   util.MaskString(catalogName),\n\t\t\t},\n\t\t},\n\t})\n\treturn failures\n}\n\nfunc ValidateClusterCatalog(failures []common.Failure, catalog *common.ClusterCatalog) ([]common.Failure, error) {\n\tif !isValidImageRef(catalog.Spec.Source.Image.Ref) {\n\t\tfailures = addCatalogFailure(failures, catalog.Name, fmt.Errorf(\"invalid image ref format in spec.source.image.ref: %s\", catalog.Spec.Source.Image.Ref))\n\t}\n\n\t// Check status.resolvedSource.image.ref ends with @sha256:...\n\tif catalog.Status.ResolvedSource != nil {\n\t\tif catalog.Status.ResolvedSource.Image.Ref == \"\" {\n\t\t\tfailures = addCatalogFailure(failures, catalog.Name, fmt.Errorf(\"missing status.resolvedSource.image.ref\"))\n\t\t}\n\t\tif !regexp.MustCompile(`@sha256:[a-f0-9]{64}$`).MatchString(catalog.Status.ResolvedSource.Image.Ref) {\n\t\t\tfailures = addCatalogFailure(failures, catalog.Name, fmt.Errorf(\"status.resolvedSource.image.ref must end with @sha256:<digest>\"))\n\t\t}\n\t}\n\n\tfor _, condition := range catalog.Status.Conditions {\n\t\tif condition.Status != \"True\" && condition.Type == \"Serving\" {\n\t\t\tfailures = addCatalogConditionFailure(failures, catalog.Name, condition)\n\t\t}\n\t\tif condition.Type == \"Progressing\" && condition.Reason != \"Succeeded\" {\n\t\t\tfailures = addCatalogConditionFailure(failures, catalog.Name, condition)\n\t\t}\n\t}\n\n\treturn failures, nil\n}\n\n// isValidImageRef does a simple regex check to validate image refs\nfunc isValidImageRef(ref string) bool {\n\tpattern := `^([a-zA-Z0-9\\-\\.]+(?::[0-9]+)?/)?([a-z0-9]+(?:[._\\-\\/][a-z0-9]+)*)(:[\\w][\\w.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$`\n\treturn regexp.MustCompile(pattern).MatchString(ref)\n}\n"
  },
  {
    "path": "pkg/analyzer/clustercatalog_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestClusterCatalogAnalyzer(t *testing.T) {\n\tgvr := schema.GroupVersionResource{\n\t\tGroup:    \"olm.operatorframework.io\",\n\t\tVersion:  \"v1\",\n\t\tResource: \"clustercatalogs\",\n\t}\n\n\tscheme := runtime.NewScheme()\n\n\tdynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(\n\t\tscheme,\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\tgvr: \"ClusterCatalogList\",\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterCatalog\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Valid ClusterCatalog\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"availabilityMode\": \"Available\",\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"type\": \"Image\",\n\t\t\t\t\t\t\"image\": map[string]interface{}{\n\t\t\t\t\t\t\t\"ref\":                 \"registry.redhat.io/redhat/community-operator-index:v4.19\",\n\t\t\t\t\t\t\t\"pollIntervalMinutes\": float64(10),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Succeeded\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Serving\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Available\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterCatalog\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Invalid availabilityMode\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"availabilityMode\": \"test\",\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"type\": \"Image\",\n\t\t\t\t\t\t\"image\": map[string]interface{}{\n\t\t\t\t\t\t\t\"ref\":                 \"registry.redhat.io/redhat/community-operator-index:v4.19\",\n\t\t\t\t\t\t\t\"pollIntervalMinutes\": float64(10),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Retrying\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterCatalog\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Invalid pollIntervalMinutes\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"availabilityMode\": \"Available\",\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"type\": \"Image\",\n\t\t\t\t\t\t\"image\": map[string]interface{}{\n\t\t\t\t\t\t\t\"ref\":                 \"registry.redhat.io/redhat/community-operator-index:v4.19\",\n\t\t\t\t\t\t\t\"pollIntervalMinutes\": float64(0),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Retrying\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterCatalog\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Invalid image reference\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"availabilityMode\": \"Available\",\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"type\": \"Image\",\n\t\t\t\t\t\t\"image\": map[string]interface{}{\n\t\t\t\t\t\t\t\"ref\":                 \"quay.io/test/community-operator-index:v4.19\",\n\t\t\t\t\t\t\t\"pollIntervalMinutes\": float64(10),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Retrying\",\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\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient:        fake.NewSimpleClientset(),\n\t\t\tDynamicClient: dynamicClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"test\",\n\t}\n\n\tccAnalyzer := ClusterCatalogAnalyzer{}\n\tresults, err := ccAnalyzer.Analyze(config)\n\tfor _, res := range results {\n\t\tfmt.Printf(\"Result: %s | Failures: %d\\n\", res.Name, len(res.Error))\n\t\tfor _, err := range res.Error {\n\t\t\tfmt.Printf(\"  - %s\\n\", err)\n\t\t}\n\t}\n\trequire.NoError(t, err)\n\trequire.Equal(t, 3, len(results))\n}\n"
  },
  {
    "path": "pkg/analyzer/clusterextension.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\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/apimachinery/pkg/runtime/schema\"\n)\n\ntype ClusterExtensionAnalyzer struct{}\n\nfunc (ClusterExtensionAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"ClusterExtension\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tvar clusterExtensionGVR = schema.GroupVersionResource{\n\t\tGroup:    \"olm.operatorframework.io\",\n\t\tVersion:  \"v1\",\n\t\tResource: \"clusterextensions\",\n\t}\n\tif a.Client == nil {\n\t\treturn nil, fmt.Errorf(\"client is nil in ClusterExtensionAnalyzer\")\n\t}\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in ClusterExtensionAnalyzer\")\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().Resource(clusterExtensionGVR).Namespace(\"\").List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, item := range list.Items {\n\t\tvar failures []common.Failure\n\t\textension, err := ConvertToClusterExtension(&item)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"ClusterExtension: %s | Source: %s\\n\", extension.Name, extension.Spec.Source.Catalog.PackageName)\n\t\tfailures, err = ValidateClusterExtension(failures, extension)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[extension.Name] = common.PreAnalysis{\n\t\t\t\tExtension:      *extension,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, extension.Name, \"\").Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Node.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, err\n}\n\nfunc ConvertToClusterExtension(u *unstructured.Unstructured) (*common.ClusterExtension, error) {\n\tvar ce common.ClusterExtension\n\terr := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ce)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert to ClusterExtension: %w\", err)\n\t}\n\treturn &ce, nil\n}\n\nfunc addExtensionConditionFailure(failures []common.Failure, extensionName string, extensionCondition metav1.Condition) []common.Failure {\n\tfailures = append(failures, common.Failure{\n\t\tText: fmt.Sprintf(\"OLMv1 ClusterExtension: %s has condition of type %s, reason %s: %s\", extensionName, extensionCondition.Type, extensionCondition.Reason, extensionCondition.Message),\n\t\tSensitive: []common.Sensitive{\n\t\t\t{\n\t\t\t\tUnmasked: extensionName,\n\t\t\t\tMasked:   util.MaskString(extensionName),\n\t\t\t},\n\t\t},\n\t})\n\treturn failures\n}\n\nfunc addExtensionFailure(failures []common.Failure, extensionName string, err error) []common.Failure {\n\tfailures = append(failures, common.Failure{\n\t\tText: fmt.Sprintf(\"%s has error: %s\", extensionName, err.Error()),\n\t\tSensitive: []common.Sensitive{\n\t\t\t{\n\t\t\t\tUnmasked: extensionName,\n\t\t\t\tMasked:   util.MaskString(extensionName),\n\t\t\t},\n\t\t},\n\t})\n\treturn failures\n}\n\nfunc ValidateClusterExtension(failures []common.Failure, extension *common.ClusterExtension) ([]common.Failure, error) {\n\tif extension.Spec.Source.Catalog != nil && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != \"CatalogProvided\" && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != \"SelfCertified\" {\n\t\tfailures = addExtensionFailure(failures, extension.Name, fmt.Errorf(\"invalid or missing extension.Spec.Source.Catalog.UpgradeConstraintPolicy (expecting 'SelfCertified' or 'CatalogProvided')\"))\n\t}\n\n\tif extension.Spec.Source.SourceType != \"Catalog\" {\n\t\tfailures = addExtensionFailure(failures, extension.Name, fmt.Errorf(\"invalid or missing spec.source.sourceType (expecting 'Catalog')\"))\n\t}\n\n\tfor _, condition := range extension.Status.Conditions {\n\t\tif condition.Status != \"True\" && condition.Type == \"Installed\" {\n\t\t\tfailures = addExtensionConditionFailure(failures, extension.Name, condition)\n\t\t}\n\t\tif condition.Type == \"Progressing\" && condition.Reason != \"Succeeded\" {\n\t\t\tfailures = addExtensionConditionFailure(failures, extension.Name, condition)\n\t\t}\n\t}\n\n\treturn failures, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/clusterextension_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestClusterExtensionAnalyzer(t *testing.T) {\n\tgvr := schema.GroupVersionResource{\n\t\tGroup:    \"olm.operatorframework.io\",\n\t\tVersion:  \"v1\",\n\t\tResource: \"clusterextensions\",\n\t}\n\n\tscheme := runtime.NewScheme()\n\n\tdynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(\n\t\tscheme,\n\t\tmap[schema.GroupVersionResource]string{\n\t\t\tgvr: \"ClusterExtensionList\",\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterExtension\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Valid SelfCertified ClusterExtension\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"sourceType\": \"Catalog\",\n\t\t\t\t\t\t\"catalog\": map[string]interface{}{\n\t\t\t\t\t\t\t\"upgradeConstraintPolicy\": \"SelfCertified\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Installed\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Succeeded\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterExtension\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Valid CatalogProvided ClusterExtension\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"sourceType\": \"Catalog\",\n\t\t\t\t\t\t\"catalog\": map[string]interface{}{\n\t\t\t\t\t\t\t\"upgradeConstraintPolicy\": \"CatalogProvided\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Installed\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Succeeded\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterExtension\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Invalid UpgradeConstraintPolicy\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"sourceType\": \"Catalog\",\n\t\t\t\t\t\t\"catalog\": map[string]interface{}{\n\t\t\t\t\t\t\t\"upgradeConstraintPolicy\": \"InvalidPolicy\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Retrying\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Installed\",\n\t\t\t\t\t\t\t\"status\": \"False\",\n\t\t\t\t\t\t\t\"reason\": \"Failed\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&unstructured.Unstructured{\n\t\t\tObject: map[string]interface{}{\n\t\t\t\t\"apiVersion\": \"olm.operatorframework.io/v1\",\n\t\t\t\t\"kind\":       \"ClusterExtension\",\n\t\t\t\t\"metadata\": map[string]interface{}{\n\t\t\t\t\t\"name\": \"Invalid SourceType\",\n\t\t\t\t},\n\t\t\t\t\"spec\": map[string]interface{}{\n\t\t\t\t\t\"source\": map[string]interface{}{\n\t\t\t\t\t\t\"sourceType\": \"Git\",\n\t\t\t\t\t\t\"catalog\": map[string]interface{}{\n\t\t\t\t\t\t\t\"upgradeConstraintPolicy\": \"CatalogProvided\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t\"status\": map[string]interface{}{\n\t\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Progressing\",\n\t\t\t\t\t\t\t\"status\": \"True\",\n\t\t\t\t\t\t\t\"reason\": \"Retrying\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmap[string]interface{}{\n\t\t\t\t\t\t\t\"type\":   \"Installed\",\n\t\t\t\t\t\t\t\"status\": \"False\",\n\t\t\t\t\t\t\t\"reason\": \"Failed\",\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\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient:        fake.NewSimpleClientset(),\n\t\t\tDynamicClient: dynamicClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"test\",\n\t}\n\n\tceAnalyzer := ClusterExtensionAnalyzer{}\n\tresults, err := ceAnalyzer.Analyze(config)\n\tfor _, res := range results {\n\t\tfmt.Printf(\"Result: %s | Failures: %d\\n\", res.Name, len(res.Error))\n\t\tfor _, err := range res.Error {\n\t\t\tfmt.Printf(\"  - %s\\n\", err)\n\t\t}\n\t}\n\trequire.NoError(t, err)\n\trequire.Equal(t, 2, len(results))\n}\n"
  },
  {
    "path": "pkg/analyzer/clusterserviceversion.go",
    "content": "package analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\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\ntype ClusterServiceVersionAnalyzer struct{}\n\nvar csvGVR = schema.GroupVersionResource{\n\tGroup: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"clusterserviceversions\",\n}\n\nfunc (ClusterServiceVersionAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"ClusterServiceVersion\"\n\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in %s analyzer\", kind)\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().\n\t\tResource(csvGVR).Namespace(metav1.NamespaceAll).\n\t\tList(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar results []common.Result\n\tfor _, item := range list.Items {\n\t\tns := item.GetNamespace()\n\t\tname := item.GetName()\n\t\tphase, _, _ := unstructured.NestedString(item.Object, \"status\", \"phase\")\n\n\t\tvar failures []common.Failure\n\t\tif phase != \"\" && phase != \"Succeeded\" {\n\t\t\t// Superfície de condições para contexto\n\t\t\tif conds, _, _ := unstructured.NestedSlice(item.Object, \"status\", \"conditions\"); len(conds) > 0 {\n\t\t\t\tif msg := pickWorstCondition(conds); msg != \"\" {\n\t\t\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"phase=%q: %s\", phase, msg)})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"phase=%q (see status.conditions)\", phase)})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  kind,\n\t\t\t\tName:  ns + \"/\" + name,\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// reaproveitamos o heurístico já usado em outros pontos\nfunc pickWorstCondition(conds []interface{}) string {\n\tfor _, c := range conds {\n\t\tm, ok := c.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif s, _ := m[\"status\"].(string); s == \"True\" {\n\t\t\tcontinue\n\t\t}\n\t\tr, _ := m[\"reason\"].(string)\n\t\tmsg, _ := m[\"message\"].(string)\n\t\tif r == \"\" && msg == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif r != \"\" && msg != \"\" {\n\t\t\treturn r + \": \" + msg\n\t\t}\n\t\treturn r + msg\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/analyzer/clusterserviceversion_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n)\n\nfunc TestClusterServiceVersionAnalyzer(t *testing.T) {\n\tok := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"ClusterServiceVersion\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"ok\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\"phase\": \"Succeeded\"},\n\t\t},\n\t}\n\n\tbad := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"ClusterServiceVersion\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"bad\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"phase\": \"Failed\",\n\t\t\t\t// IMPORTANT: conditions must be []interface{}, not []map[string]any\n\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"status\":  \"False\",\n\t\t\t\t\t\t\"reason\":  \"ErrorResolving\",\n\t\t\t\t\t\t\"message\": \"missing dep\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"clusterserviceversions\"}: \"ClusterServiceVersionList\",\n\t}\n\n\t// Use a non-nil scheme with dynamicfake\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, ok, bad)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (ClusterServiceVersionAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\n\tif len(res) != 1 {\n\t\tt.Fatalf(\"expected 1 result, got %d\", len(res))\n\t}\n\tif res[0].Kind != \"ClusterServiceVersion\" || !strings.Contains(res[0].Name, \"ns1/bad\") {\n\t\tt.Fatalf(\"unexpected result: %#v\", res[0])\n\t}\n\tif len(res[0].Error) == 0 || !strings.Contains(res[0].Error[0].Text, \"missing dep\") {\n\t\tt.Fatalf(\"expected 'missing dep' in failure, got %#v\", res[0].Error)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/configmap.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype ConfigMapAnalyzer struct{}\n\nfunc (ConfigMapAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"ConfigMap\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// Get all ConfigMaps in the namespace\n\tconfigMaps, err := a.Client.GetClient().CoreV1().ConfigMaps(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Get all Pods to check ConfigMap usage\n\tpods, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar results []common.Result\n\n\t// Track which ConfigMaps are used\n\tusedConfigMaps := make(map[string]bool)\n\tconfigMapUsage := make(map[string][]string) // maps ConfigMap name to list of pods using it\n\n\t// Analyze ConfigMap usage in Pods\n\tfor _, pod := range pods.Items {\n\t\t// Check volume mounts\n\t\tfor _, volume := range pod.Spec.Volumes {\n\t\t\tif volume.ConfigMap != nil {\n\t\t\t\tusedConfigMaps[volume.ConfigMap.Name] = true\n\t\t\t\tconfigMapUsage[volume.ConfigMap.Name] = append(configMapUsage[volume.ConfigMap.Name], pod.Name)\n\t\t\t}\n\t\t}\n\n\t\t// Check environment variables\n\t\tfor _, container := range pod.Spec.Containers {\n\t\t\tfor _, env := range container.EnvFrom {\n\t\t\t\tif env.ConfigMapRef != nil {\n\t\t\t\t\tusedConfigMaps[env.ConfigMapRef.Name] = true\n\t\t\t\t\tconfigMapUsage[env.ConfigMapRef.Name] = append(configMapUsage[env.ConfigMapRef.Name], pod.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, env := range container.Env {\n\t\t\t\tif env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil {\n\t\t\t\t\tusedConfigMaps[env.ValueFrom.ConfigMapKeyRef.Name] = true\n\t\t\t\t\tconfigMapUsage[env.ValueFrom.ConfigMapKeyRef.Name] = append(configMapUsage[env.ValueFrom.ConfigMapKeyRef.Name], pod.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Analyze each ConfigMap\n\tfor _, cm := range configMaps.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for unused ConfigMaps\n\t\tif !usedConfigMaps[cm.Name] {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"ConfigMap %s is not used by any pods in the namespace\", cm.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\t// Check for empty ConfigMaps\n\t\tif len(cm.Data) == 0 && len(cm.BinaryData) == 0 {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"ConfigMap %s is empty\", cm.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\t// Check for large ConfigMaps (over 1MB)\n\t\ttotalSize := 0\n\t\tfor _, value := range cm.Data {\n\t\t\ttotalSize += len(value)\n\t\t}\n\t\tfor _, value := range cm.BinaryData {\n\t\t\ttotalSize += len(value)\n\t\t}\n\t\tif totalSize > 1024*1024 { // 1MB\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"ConfigMap %s is larger than 1MB (%d bytes)\", cm.Name, totalSize),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  kind,\n\t\t\t\tName:  fmt.Sprintf(\"%s/%s\", cm.Namespace, cm.Name),\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, cm.Name, cm.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/configmap_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestConfigMapAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tnamespace      string\n\t\tconfigMaps     []v1.ConfigMap\n\t\tpods           []v1.Pod\n\t\texpectedErrors int\n\t}{\n\t\t{\n\t\t\tname:      \"unused configmap\",\n\t\t\tnamespace: \"default\",\n\t\t\tconfigMaps: []v1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"unused-cm\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\n\t\t\t\t\t\t\"key\": \"value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"empty configmap\",\n\t\t\tnamespace: \"default\",\n\t\t\tconfigMaps: []v1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"empty-cm\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"large configmap\",\n\t\t\tnamespace: \"default\",\n\t\t\tconfigMaps: []v1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"large-cm\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\n\t\t\t\t\t\t\"key\": string(make([]byte, 1024*1024+1)), // 1MB + 1 byte\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"used configmap\",\n\t\t\tnamespace: \"default\",\n\t\t\tconfigMaps: []v1.ConfigMap{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"used-cm\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tData: map[string]string{\n\t\t\t\t\t\t\"key\": \"value\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpods: []v1.Pod{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-pod\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"test-container\",\n\t\t\t\t\t\t\t\tEnvFrom: []v1.EnvFromSource{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tConfigMapRef: &v1.ConfigMapEnvSource{\n\t\t\t\t\t\t\t\t\t\t\tLocalObjectReference: v1.LocalObjectReference{\n\t\t\t\t\t\t\t\t\t\t\t\tName: \"used-cm\",\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\texpectedErrors: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclient := fake.NewSimpleClientset()\n\n\t\t\t// Create test resources\n\t\t\tfor _, cm := range tt.configMaps {\n\t\t\t\t_, err := client.CoreV1().ConfigMaps(tt.namespace).Create(context.TODO(), &cm, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor _, pod := range tt.pods {\n\t\t\t\t_, err := client.CoreV1().Pods(tt.namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tanalyzer := ConfigMapAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(common.Analyzer{\n\t\t\t\tClient:    &kubernetes.Client{Client: client},\n\t\t\t\tContext:   context.TODO(),\n\t\t\t\tNamespace: tt.namespace,\n\t\t\t})\n\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, tt.expectedErrors, len(results))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/cronjob.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tcron \"github.com/robfig/cron/v3\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype CronJobAnalyzer struct{}\n\nfunc (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"CronJob\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"batch\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tcronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, cronJob := range cronJobList.Items {\n\t\tvar failures []common.Failure\n\t\tif cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.suspend\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"CronJob %s is suspended\", cronJob.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: cronJob.Namespace,\n\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Namespace),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: cronJob.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\t// check the schedule format\n\t\t\tif _, err := CheckCronScheduleIsValid(cronJob.Spec.Schedule); err != nil {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.schedule\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"CronJob %s has an invalid schedule: %s\", cronJob.Name, err.Error()),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: cronJob.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: cronJob.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Name),\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\t// check the starting deadline\n\t\t\tif cronJob.Spec.StartingDeadlineSeconds != nil {\n\t\t\t\tdeadline := time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second\n\t\t\t\tif deadline < 0 {\n\t\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.startingDeadlineSeconds\")\n\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:          fmt.Sprintf(\"CronJob %s has a negative starting deadline\", cronJob.Name),\n\t\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: cronJob.Namespace,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Namespace),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: cronJob.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(cronJob.Name),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", cronJob.Namespace, cronJob.Name)] = common.PreAnalysis{\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, cronJob.Name, cronJob.Namespace).Set(float64(len(failures)))\n\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tcurrentAnalysis := common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\n// Check CRON schedule format\nfunc CheckCronScheduleIsValid(schedule string) (bool, error) {\n\t_, err := cron.ParseStandard(schedule)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/cronjob_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestCronJobAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       common.Analyzer\n\t\texpectations []struct {\n\t\t\tname          string\n\t\t\tfailuresCount int\n\t\t}\n\t}{\n\t\t{\n\t\t\tname: \"Suspended CronJob\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.CronJob{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"suspended-job\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\t\t\t\t\tSchedule: \"*/5 * * * *\",\n\t\t\t\t\t\t\t\tSuspend:  boolPtr(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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/suspended-job\",\n\t\t\t\t\tfailuresCount: 1, // One failure for being suspended\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid schedule format\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.CronJob{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"invalid-schedule\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\t\t\t\t\tSchedule: \"invalid-cron\", // Invalid cron format\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/invalid-schedule\",\n\t\t\t\t\tfailuresCount: 1, // One failure for invalid schedule\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Negative starting deadline\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.CronJob{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"negative-deadline\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\t\t\t\t\tSchedule:                \"*/5 * * * *\",\n\t\t\t\t\t\t\t\tStartingDeadlineSeconds: int64Ptr(-60), // Negative deadline\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/negative-deadline\",\n\t\t\t\t\tfailuresCount: 1, // One failure for negative deadline\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Valid CronJob\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.CronJob{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"valid-job\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\t\t\t\t\tSchedule: \"*/5 * * * *\", // Valid cron format\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t// No expectations for valid job\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple issues\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.CronJob{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"multiple-issues\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\t\t\t\t\tSchedule:                \"invalid-cron\",\n\t\t\t\t\t\t\t\tStartingDeadlineSeconds: int64Ptr(-60),\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/multiple-issues\",\n\t\t\t\t\tfailuresCount: 2, // Two failures: invalid schedule and negative deadline\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tanalyzer := CronJobAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(tt.config)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, results, len(tt.expectations))\n\n\t\t\t// Sort results by name for consistent comparison\n\t\t\tsort.Slice(results, func(i, j int) bool {\n\t\t\t\treturn results[i].Name < results[j].Name\n\t\t\t})\n\n\t\t\tfor i, expectation := range tt.expectations {\n\t\t\t\trequire.Equal(t, expectation.name, results[i].Name)\n\t\t\t\trequire.Len(t, results[i].Error, expectation.failuresCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCronJobAnalyzerLabelSelector(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset(\n\t\t&batchv1.CronJob{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"job-with-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule: \"invalid-cron\", // This should trigger a failure\n\t\t\t},\n\t\t},\n\t\t&batchv1.CronJob{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"job-without-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: batchv1.CronJobSpec{\n\t\t\t\tSchedule: \"invalid-cron\", // This should trigger a failure\n\t\t\t},\n\t\t},\n\t)\n\n\t// Test with label selector\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=test\",\n\t}\n\n\tanalyzer := CronJobAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/job-with-label\", results[0].Name)\n}\n\nfunc TestCheckCronScheduleIsValid(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tschedule string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"Valid schedule - every 5 minutes\",\n\t\t\tschedule: \"*/5 * * * *\",\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Valid schedule - specific time\",\n\t\t\tschedule: \"0 2 * * *\",\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Valid schedule - complex\",\n\t\t\tschedule: \"0 0 1,15 * 3\",\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Invalid schedule - wrong format\",\n\t\t\tschedule: \"invalid-cron\",\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Invalid schedule - too many fields\",\n\t\t\tschedule: \"* * * * * *\",\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Invalid schedule - empty string\",\n\t\t\tschedule: \"\",\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, err := CheckCronScheduleIsValid(tt.schedule)\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/deployment.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n)\n\n// DeploymentAnalyzer is an analyzer that checks for misconfigured Deployments\ntype DeploymentAnalyzer struct {\n}\n\n// Analyze scans all namespaces for Deployments with misconfigurations\nfunc (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Deployment\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"apps\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tdeployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, deployment := range deployments.Items {\n\t\tvar failures []common.Failure\n\t\tif *deployment.Spec.Replicas != deployment.Status.ReadyReplicas {\n\t\t\tif  deployment.Status.Replicas > *deployment.Spec.Replicas {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.replicas\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Deployment %s/%s has %d replicas in spec but %d replicas in status because status field is not updated yet after scaling and %d replicas are available with status running\", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.Replicas, deployment.Status.ReadyReplicas),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: deployment.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(deployment.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: deployment.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(deployment.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t}})\n\n\t\t\t} else {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.replicas\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Deployment %s/%s has %d replicas but %d are available with status running\", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.ReadyReplicas),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: deployment.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(deployment.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: deployment.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(deployment.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t}})\n\t\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", deployment.Namespace, deployment.Name)] = common.PreAnalysis{\n\t\t\t\tFailureDetails: failures,\n\t\t\t\tDeployment:     deployment,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, deployment.Name, deployment.Namespace).Set(float64(len(failures)))\n\t\t}\n\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/deployment_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/magiconair/properties/assert\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestDeploymentAnalyzer(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(&appsv1.Deployment{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: appsv1.DeploymentSpec{\n\t\t\tReplicas: func() *int32 { i := int32(3); return &i }(),\n\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName:  \"example-container\",\n\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\tPorts: []v1.ContainerPort{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tContainerPort: 80,\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\tStatus: appsv1.DeploymentStatus{\n\t\t\tReplicas:          2,\n\t\t\tAvailableReplicas: 1,\n\t\t},\n\t})\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tdeploymentAnalyzer := DeploymentAnalyzer{}\n\tanalysisResults, err := deploymentAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\tassert.Equal(t, analysisResults[0].Kind, \"Deployment\")\n\tassert.Equal(t, analysisResults[0].Name, \"default/example\")\n}\n\nfunc TestDeploymentAnalyzerNamespaceFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.DeploymentSpec{\n\t\t\t\tReplicas: func() *int32 { i := int32(3); return &i }(),\n\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"example-container\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tPorts: []v1.ContainerPort{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tContainerPort: 80,\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\tStatus: appsv1.DeploymentStatus{\n\t\t\t\tReplicas:          2,\n\t\t\t\tAvailableReplicas: 1,\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"other-namespace\",\n\t\t\t},\n\t\t\tSpec: appsv1.DeploymentSpec{\n\t\t\t\tReplicas: func() *int32 { i := int32(3); return &i }(),\n\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"example-container\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tPorts: []v1.ContainerPort{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tContainerPort: 80,\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\tStatus: appsv1.DeploymentStatus{\n\t\t\t\tReplicas:          2,\n\t\t\t\tAvailableReplicas: 1,\n\t\t\t},\n\t\t},\n\t)\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tdeploymentAnalyzer := DeploymentAnalyzer{}\n\tanalysisResults, err := deploymentAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\tassert.Equal(t, analysisResults[0].Kind, \"Deployment\")\n\tassert.Equal(t, analysisResults[0].Name, \"default/example\")\n}\n\nfunc TestDeploymentAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"deployment\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: appsv1.DeploymentSpec{\n\t\t\t\tReplicas: func() *int32 { i := int32(3); return &i }(),\n\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.DeploymentSpec{\n\t\t\t\tReplicas: func() *int32 { i := int32(3); return &i }(),\n\t\t\t\tTemplate: v1.PodTemplateSpec{\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=deployment\",\n\t}\n\n\tdeploymentAnalyzer := DeploymentAnalyzer{}\n\tanalysisResults, err := deploymentAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n"
  },
  {
    "path": "pkg/analyzer/events_test.go",
    "content": "package analyzer_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc FetchLatestEvent(ctx context.Context, client kubernetes.Interface, namespace, eventName string) (*v1.Event, error) {\n\t// List events in the specified namespace\n\tevents, err := client.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar latestEvent *v1.Event\n\tfor _, event := range events.Items {\n\t\t// Check if the event name matches the requested name (partial match)\n\t\tif eventName == \"\" || event.Name == eventName {\n\t\t\tif latestEvent == nil || event.LastTimestamp.Time.After(latestEvent.LastTimestamp.Time) {\n\t\t\t\tlatestEvent = &event\n\t\t\t}\n\t\t}\n\t}\n\n\t// If no matching event is found, return an error\n\tif latestEvent == nil {\n\t\treturn nil, errors.New(\"no matching events found\")\n\t}\n\treturn latestEvent, nil\n}\nfunc TestFetchLatestEvent(t *testing.T) {\n\tfakeClient := fake.NewSimpleClientset()\n\n\t// Simulating events with different timestamps\n\tevent1 := &v1.Event{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"test-event-1\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tLastTimestamp: metav1.Time{Time: time.Now()},\n\t}\n\tevent2 := &v1.Event{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"test-event-2\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tLastTimestamp: metav1.Time{Time: time.Now().Add(-time.Hour)}, // event1 should be fetched as it's newer\n\t}\n\n\t// ✅ Explicitly ensure namespace exists\n\t_, err := fakeClient.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{\n\t\tObjectMeta: metav1.ObjectMeta{Name: \"default\"},\n\t}, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create namespace: %v\", err)\n\t}\n\n\t// ✅ Ensure events are properly created and stored in the fake client\n\t_, err = fakeClient.CoreV1().Events(\"default\").Create(context.TODO(), event1, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create event1: %v\", err)\n\t}\n\n\t_, err = fakeClient.CoreV1().Events(\"default\").Create(context.TODO(), event2, metav1.CreateOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create event2: %v\", err)\n\t}\n\n\t// 🔍 Debug: Check if events exist before running FetchLatestEvent\n\tstoredEvents, _ := fakeClient.CoreV1().Events(\"default\").List(context.TODO(), metav1.ListOptions{})\n\tif len(storedEvents.Items) == 0 {\n\t\tt.Fatal(\"No events were found in the fake client. Ensure event creation is working correctly.\")\n\t}\n\n\t// Test cases\n\ttests := []struct {\n\t\tname       string\n\t\tnamespace  string\n\t\tnameToFind string\n\t\texpected   *v1.Event\n\t\tshouldFail bool\n\t}{\n\t\t{\n\t\t\tname:       \"Valid case - fetch the latest event\",\n\t\t\tnamespace:  \"default\",\n\t\t\tnameToFind: \"test-event-1\", // Match exact event name\n\t\t\texpected:   event1,         // event1 has the latest timestamp\n\t\t\tshouldFail: false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Nonexistent event\",\n\t\t\tnamespace:  \"default\",\n\t\t\tnameToFind: \"nonexistent-event\", // Should not exist\n\t\t\texpected:   nil,\n\t\t\tshouldFail: true,\n\t\t},\n\t\t{\n\t\t\tname:       \"Nonexistent namespace\",\n\t\t\tnamespace:  \"nonexistent-namespace\", // Namespace doesn't exist\n\t\t\tnameToFind: \"test-event\",\n\t\t\texpected:   nil,\n\t\t\tshouldFail: true,\n\t\t},\n\t}\n\n\t// Run tests\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Call the function to fetch the latest event\n\t\t\tevent, err := FetchLatestEvent(context.TODO(), fakeClient, tt.namespace, tt.nameToFind)\n\n\t\t\t// Handle the expected outcomes based on the test case\n\t\t\tif tt.shouldFail {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Error(\"Expected an error, but got nil\")\n\t\t\t\t}\n\t\t\t\tif event != nil {\n\t\t\t\t\tt.Errorf(\"Expected nil event, but got event: %s\", event.Name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Expected no error, but got %v\", err)\n\t\t\t\t}\n\t\t\t\tif event != nil && event.Name != tt.expected.Name {\n\t\t\t\t\tt.Errorf(\"Expected event name %s, got %s\", tt.expected.Name, event.Name)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/gateway.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\ntype GatewayAnalyzer struct{}\n\n// Gateway analyser will analyse all different Kinds and search for missing object dependencies\nfunc (GatewayAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Gateway\"\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tgtwList := &gtwapi.GatewayList{}\n\tgc := &gtwapi.GatewayClass{}\n\tclient := a.Client.CtrlClient\n\terr := gtwapi.AddToScheme(client.Scheme())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlabelSelector := util.LabelStrToSelector(a.LabelSelector)\n\tif err := client.List(a.Context, gtwList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\t// Find all unhealthy gateway Classes\n\n\tfor _, gtw := range gtwList.Items {\n\t\tvar failures []common.Failure\n\n\t\tgtwName := gtw.GetName()\n\t\tgtwNamespace := gtw.GetNamespace()\n\t\t// Check if gatewayclass exists\n\t\terr := client.Get(a.Context, ctrl.ObjectKey{Namespace: gtwNamespace, Name: string(gtw.Spec.GatewayClassName)}, gc, &ctrl.GetOptions{})\n\t\tif errors.IsNotFound(err) {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\"Gateway uses the GatewayClass %s which does not exist.\",\n\t\t\t\t\tgtw.Spec.GatewayClassName,\n\t\t\t\t),\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: string(gtw.Spec.GatewayClassName),\n\t\t\t\t\t\tMasked:   util.MaskString(string(gtw.Spec.GatewayClassName)),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\t// Check only the current conditions\n\t\t// TODO: maybe check other statuses Listeners, addresses?\n\t\tif gtw.Status.Conditions[0].Status != metav1.ConditionTrue {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\"Gateway '%s/%s' is not accepted. Message: '%s'.\",\n\t\t\t\t\tgtwNamespace,\n\t\t\t\t\tgtwName,\n\t\t\t\t\tgtw.Status.Conditions[0].Message,\n\t\t\t\t),\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: gtwNamespace,\n\t\t\t\t\t\tMasked:   util.MaskString(gtwNamespace),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: gtwName,\n\t\t\t\t\t\tMasked:   util.MaskString(gtwName),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", gtwNamespace, gtwName)] = common.PreAnalysis{\n\t\t\t\tGateway:        gtw,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, gtwName, gtwNamespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/gateway_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/magiconair/properties/assert\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tfakeclient \"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\nfunc BuildGatewayClass(name string) gtwapi.GatewayClass {\n\tGatewayClass := gtwapi.GatewayClass{}\n\tGatewayClass.Name = name\n\t// Namespace is not needed outside of this test, GatewayClass is cluster-scoped\n\tGatewayClass.Namespace = \"default\"\n\tGatewayClass.Spec.ControllerName = \"gateway.fooproxy.io/gatewayclass-controller\"\n\n\treturn GatewayClass\n}\n\nfunc BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus, labels map[string]string) gtwapi.Gateway {\n\tGateway := gtwapi.Gateway{}\n\tGateway.Name = \"foobar\"\n\tGateway.Namespace = \"default\"\n\tif labels != nil {\n\t\tGateway.Labels = labels\n\t}\n\tGateway.Spec.GatewayClassName = className\n\tGateway.Spec.Listeners = []gtwapi.Listener{\n\t\t{\n\t\t\tName:     \"proxy\",\n\t\t\tPort:     80,\n\t\t\tProtocol: gtwapi.HTTPProtocolType,\n\t\t},\n\t}\n\tCondition := metav1.Condition{\n\t\tType:    \"Accepted\",\n\t\tStatus:  status,\n\t\tMessage: \"An expected message\",\n\t\tReason:  \"Test\",\n\t}\n\tGateway.Status.Conditions = []metav1.Condition{Condition}\n\n\treturn Gateway\n}\n\nfunc TestGatewayAnalyzer(t *testing.T) {\n\tClassName := gtwapi.ObjectName(\"exists\")\n\tAcceptedStatus := metav1.ConditionTrue\n\tGatewayClass := BuildGatewayClass(string(ClassName))\n\n\tGateway := BuildGateway(ClassName, AcceptedStatus, nil)\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&Gateway,\n\t\t&GatewayClass,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := GatewayAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 0)\n\n}\n\nfunc TestMissingClassGatewayAnalyzer(t *testing.T) {\n\tClassName := gtwapi.ObjectName(\"non-existed\")\n\tAcceptedStatus := metav1.ConditionTrue\n\tGateway := BuildGateway(ClassName, AcceptedStatus, nil)\n\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&Gateway,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := GatewayAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestStatusGatewayAnalyzer(t *testing.T) {\n\tClassName := gtwapi.ObjectName(\"exists\")\n\tAcceptedStatus := metav1.ConditionUnknown\n\tGatewayClass := BuildGatewayClass(string(ClassName))\n\n\tGateway := BuildGateway(ClassName, AcceptedStatus, nil)\n\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&Gateway,\n\t\t&GatewayClass,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := GatewayAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvar errorFound bool\n\twant := \"Gateway 'default/foobar' is not accepted. Message: 'An expected message'.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%v> , not found in Gateway's analysis results\", want)\n\t}\n}\n\nfunc TestGatewayAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tClassName := gtwapi.ObjectName(\"non-existed\")\n\tAcceptedStatus := metav1.ConditionTrue\n\n\tGateway := BuildGateway(ClassName, AcceptedStatus, map[string]string{\"app\": \"gateway\"})\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&Gateway,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := GatewayAnalyzer{}\n\t// without label selector should return 1 result\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n\t// with label selector should return 1 result\n\tconfig = common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=gateway\",\n\t}\n\tanalysisResults, err = analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n\t// with wrong label selector should return 0 result\n\tconfig = common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=wrong\",\n\t}\n\tanalysisResults, err = analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 0)\n\n}\n"
  },
  {
    "path": "pkg/analyzer/gatewayclass.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\ntype GatewayClassAnalyzer struct{}\n\n// Gateway analyser will analyse all different Kinds and search for missing object dependencies\nfunc (GatewayClassAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"GatewayClass\"\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tgcList := &gtwapi.GatewayClassList{}\n\tclient := a.Client.CtrlClient\n\terr := gtwapi.AddToScheme(client.Scheme())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlabelSelector := util.LabelStrToSelector(a.LabelSelector)\n\tif err := client.List(a.Context, gcList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\t// Find all unhealthy gateway Classes\n\n\tfor _, gc := range gcList.Items {\n\t\tvar failures []common.Failure\n\n\t\tgcName := gc.GetName()\n\t\t// Check only the current condition\n\t\tif gc.Status.Conditions[0].Status != metav1.ConditionTrue {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\"GatewayClass '%s' with a controller name '%s' is not accepted. Message: '%s'.\",\n\t\t\t\t\tgcName,\n\t\t\t\t\tgc.Spec.ControllerName,\n\t\t\t\t\tgc.Status.Conditions[0].Message,\n\t\t\t\t),\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: gcName,\n\t\t\t\t\t\tMasked:   util.MaskString(gcName),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[gcName] = common.PreAnalysis{\n\t\t\t\tGatewayClass:   gc,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, gcName, \"\").Set(float64(len(failures)))\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/gatewayclass_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/assert\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\tfakeclient \"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\n// Testing with the fake dynamic client if GatewayClasses have an accepted status\nfunc TestGatewayClassAnalyzer(t *testing.T) {\n\tGatewayClass := &gtwapi.GatewayClass{}\n\tGatewayClass.Name = \"foobar\"\n\tGatewayClass.Spec.ControllerName = \"gateway.fooproxy.io/gatewayclass-controller\"\n\t// Initialize Conditions slice before setting properties\n\tBadCondition := metav1.Condition{\n\t\tType:    \"Accepted\",\n\t\tStatus:  \"Uknown\",\n\t\tMessage: \"Waiting for controller\",\n\t\tReason:  \"Pending\",\n\t}\n\tGatewayClass.Status.Conditions = []metav1.Condition{BadCondition}\n\t// Create a GatewayClassAnalyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass).Build()\n\n\tanalyzerInstance := GatewayClassAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestGatewayClassAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tcondition := metav1.Condition{\n\t\tType:    \"Accepted\",\n\t\tStatus:  \"Ready\",\n\t\tMessage: \"Ready\",\n\t\tReason:  \"Ready\",\n\t}\n\n\t// Create two GatewayClasses with different labels\n\tGatewayClass := &gtwapi.GatewayClass{}\n\tGatewayClass.Name = \"foobar\"\n\tGatewayClass.Spec.ControllerName = \"gateway.fooproxy.io/gatewayclass-controller\"\n\tGatewayClass.Labels = map[string]string{\"app\": \"gatewayclass\"}\n\tGatewayClass.Status.Conditions = []metav1.Condition{condition}\n\n\tGatewayClass2 := &gtwapi.GatewayClass{}\n\tGatewayClass2.Name = \"foobar2\"\n\tGatewayClass2.Spec.ControllerName = \"gateway.fooproxy.io/gatewayclass-controller\"\n\tGatewayClass2.Status.Conditions = []metav1.Condition{condition}\n\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass, GatewayClass2).Build()\n\n\tanalyzerInstance := GatewayClassAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=gatewayclass\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n"
  },
  {
    "path": "pkg/analyzer/hpa.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tautoscalingv2 \"k8s.io/api/autoscaling/v2\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype HpaAnalyzer struct{}\n\nfunc (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"HorizontalPodAutoscaler\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"autoscaling\",\n\t\t\tVersion: \"v2\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tlist, err := a.Client.GetClient().AutoscalingV2().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, hpa := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t//check the error from status field\n\t\tconditions := hpa.Status.Conditions\n\t\tfor _, condition := range conditions {\n\t\t\t// https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#appendix-horizontal-pod-autoscaler-status-conditions\n\t\t\tswitch condition.Type {\n\t\t\tcase autoscalingv2.ScalingLimited:\n\t\t\t\tif condition.Status == corev1.ConditionTrue {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      condition.Message,\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif condition.Status == corev1.ConditionFalse {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      condition.Message,\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// check ScaleTargetRef exist\n\t\tscaleTargetRef := hpa.Spec.ScaleTargetRef\n\t\tvar podInfo PodInfo\n\n\t\tswitch scaleTargetRef.Kind {\n\t\tcase \"Deployment\":\n\t\t\tdeployment, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = DeploymentInfo{deployment}\n\t\t\t}\n\t\tcase \"ReplicationController\":\n\t\t\trc, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = ReplicationControllerInfo{rc}\n\t\t\t}\n\t\tcase \"ReplicaSet\":\n\t\t\trs, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = ReplicaSetInfo{rs}\n\t\t\t}\n\t\tcase \"StatefulSet\":\n\t\t\tss, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = StatefulSetInfo{ss}\n\t\t\t}\n\t\tdefault:\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"HorizontalPodAutoscaler uses %s as ScaleTargetRef which is not an option.\", scaleTargetRef.Kind),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\tif podInfo == nil {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.scaleTargetRef\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.\", scaleTargetRef.Kind, scaleTargetRef.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: scaleTargetRef.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(scaleTargetRef.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\tcontainers := len(podInfo.GetPodSpec().Containers)\n\t\t\tfor _, container := range podInfo.GetPodSpec().Containers {\n\t\t\t\tif container.Resources.Requests == nil || container.Resources.Limits == nil {\n\t\t\t\t\tcontainers--\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif containers <= 0 {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.scaleTargetRef.kind\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"%s %s/%s does not have resource configured.\", scaleTargetRef.Kind, a.Namespace, scaleTargetRef.Name),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: scaleTargetRef.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(scaleTargetRef.Name),\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}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", hpa.Namespace, hpa.Name)] = common.PreAnalysis{\n\t\t\t\tHorizontalPodAutoscalers: hpa,\n\t\t\t\tFailureDetails:           failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, hpa.Name, hpa.Namespace).Set(float64(len(failures)))\n\t\t}\n\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\ntype PodInfo interface {\n\tGetPodSpec() corev1.PodSpec\n}\n\ntype DeploymentInfo struct {\n\t*appsv1.Deployment\n}\n\nfunc (d DeploymentInfo) GetPodSpec() corev1.PodSpec {\n\treturn d.Spec.Template.Spec\n}\n\n// define a structure for ReplicationController\ntype ReplicationControllerInfo struct {\n\t*corev1.ReplicationController\n}\n\nfunc (rc ReplicationControllerInfo) GetPodSpec() corev1.PodSpec {\n\treturn rc.Spec.Template.Spec\n}\n\n// define a structure for ReplicaSet\ntype ReplicaSetInfo struct {\n\t*appsv1.ReplicaSet\n}\n\nfunc (rs ReplicaSetInfo) GetPodSpec() corev1.PodSpec {\n\treturn rs.Spec.Template.Spec\n}\n\n// define a structure for StatefulSet\ntype StatefulSetInfo struct {\n\t*appsv1.StatefulSet\n}\n\n// implement PodInfo for StatefulSetInfo\nfunc (ss StatefulSetInfo) GetPodSpec() corev1.PodSpec {\n\treturn ss.Spec.Template.Spec\n}\n"
  },
  {
    "path": "pkg/analyzer/hpa_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/magiconair/properties/assert\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tautoscalingv2 \"k8s.io/api/autoscaling/v2\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestHPAAnalyzer(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestHPAAnalyzerWithMultipleHPA(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t},\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example-2\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t},\n\t)\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 2)\n}\n\nfunc TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"unsupported\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\tfor _, analysis := range analysisResults {\n\t\tfor _, err := range analysis.Error {\n\t\t\tif strings.Contains(err.Text, \"which is not an option.\") {\n\t\t\t\terrorFound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !errorFound {\n\t\tt.Error(\"expected error 'does not possible option.' not found in analysis results\")\n\t}\n}\n\nfunc TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"non-existent\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\tfor _, analysis := range analysisResults {\n\t\tfor _, err := range analysis.Error {\n\t\t\tif strings.Contains(err.Text, \"does not exist.\") {\n\t\t\t\terrorFound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !errorFound {\n\t\tt.Error(\"expected error 'does not exist.' not found in analysis results\")\n\t}\n}\n\nfunc TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\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\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tResources: corev1.ResourceRequirements{\n\t\t\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"100m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"128Mi\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tLimits: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"200m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"256Mi\"),\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\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tfor _, analysis := range analysisResults {\n\t\tassert.Equal(t, len(analysis.Error), 0)\n\t}\n}\n\nfunc TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"ReplicationController\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&corev1.ReplicationController{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: corev1.ReplicationControllerSpec{\n\t\t\t\tTemplate: &corev1.PodTemplateSpec{\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:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tResources: corev1.ResourceRequirements{\n\t\t\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"100m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"128Mi\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tLimits: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"200m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"256Mi\"),\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\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tfor _, analysis := range analysisResults {\n\t\tassert.Equal(t, len(analysis.Error), 0)\n\t}\n}\n\nfunc TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"ReplicaSet\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.ReplicaSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: appsv1.ReplicaSetSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\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:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tResources: corev1.ResourceRequirements{\n\t\t\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"100m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"128Mi\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tLimits: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"200m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"256Mi\"),\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\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tfor _, analysis := range analysisResults {\n\t\tassert.Equal(t, len(analysis.Error), 0)\n\t}\n}\n\nfunc TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"StatefulSet\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tTemplate: corev1.PodTemplateSpec{\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:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\n\t\t\t\t\t\t\t\tResources: corev1.ResourceRequirements{\n\t\t\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"100m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"128Mi\"),\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tLimits: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\t\t\"cpu\":    resource.MustParse(\"200m\"),\n\t\t\t\t\t\t\t\t\t\t\"memory\": resource.MustParse(\"256Mi\"),\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\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tfor _, analysis := range analysisResults {\n\t\tassert.Equal(t, len(analysis.Error), 0)\n\t}\n}\n\nfunc TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *testing.T) {\n\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\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\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\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\thpaAnalyzer := HpaAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\tfor _, analysis := range analysisResults {\n\t\tfor _, err := range analysis.Error {\n\t\t\tif strings.Contains(err.Text, \"does not have resource configured.\") {\n\t\t\t\terrorFound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif errorFound {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !errorFound {\n\t\t\tt.Error(\"expected error 'does not have resource configured.' not found in analysis results\")\n\t\t}\n\t}\n}\n\nfunc TestHPAAnalyzerNamespaceFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t},\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"other-namespace\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestHPAAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"hpa\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t)\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=hpa\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestHPAAnalyzerStatusFieldAbleToScale(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatus: autoscalingv2.HorizontalPodAutoscalerStatus{\n\t\t\t\tConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    \"AbleToScale\",\n\t\t\t\t\t\tStatus:  \"False\",\n\t\t\t\t\t\tMessage: \"test reason\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestHPAAnalyzerStatusFieldScalingActive(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatus: autoscalingv2.HorizontalPodAutoscalerStatus{\n\t\t\t\tConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingActive,\n\t\t\t\t\t\tStatus:  \"False\",\n\t\t\t\t\t\tMessage: \"test reason\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestHPAAnalyzerStatusFieldScalingLimited(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatus: autoscalingv2.HorizontalPodAutoscalerStatus{\n\t\t\t\tConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingLimited,\n\t\t\t\t\t\tStatus:  \"False\",\n\t\t\t\t\t\tMessage: \"test reason\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestHPAAnalyzerStatusField(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatus: autoscalingv2.HorizontalPodAutoscalerStatus{\n\t\t\t\tConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.AbleToScale,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"recommended size matches current size\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingActive,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"the HPA was able to successfully calculate a replica count\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingLimited,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"the desired replica count is less than the minimum replica count\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n\n}\n\nfunc TestHPAAnalyzerStatusScalingLimitedError(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&autoscalingv2.HorizontalPodAutoscaler{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\n\t\t\t},\n\t\t\tSpec: autoscalingv2.HorizontalPodAutoscalerSpec{\n\t\t\t\tScaleTargetRef: autoscalingv2.CrossVersionObjectReference{\n\t\t\t\t\tKind: \"Deployment\",\n\t\t\t\t\tName: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tStatus: autoscalingv2.HorizontalPodAutoscalerStatus{\n\t\t\t\tConditions: []autoscalingv2.HorizontalPodAutoscalerCondition{\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.AbleToScale,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"recommended size matches current size\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingActive,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"the HPA was able to successfully calculate a replica count\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tType:    autoscalingv2.ScalingLimited,\n\t\t\t\t\t\tStatus:  \"True\",\n\t\t\t\t\t\tMessage: \"the desired replica count is less than the minimum replica count\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:        \"example\",\n\t\t\t\tNamespace:   \"default\",\n\t\t\t\tAnnotations: map[string]string{},\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\t\tContainers: []corev1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName:  \"example\",\n\t\t\t\t\t\t\t\tImage: \"nginx\",\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\thpaAnalyzer := HpaAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := hpaAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvar errorFound bool\n\twant := \"the desired replica count is less than the minimum replica count\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%v> , not found in HorizontalPodAutoscaler's analysis results\", want)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/httproute.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\ntype HTTPRouteAnalyzer struct{}\n\n// Gateway analyser will analyse all different Kinds and search for missing object dependencies\nfunc (HTTPRouteAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"HTTPRoute\"\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\trouteList := &gtwapi.HTTPRouteList{}\n\tgtw := &gtwapi.Gateway{}\n\tservice := &corev1.Service{}\n\tclient := a.Client.CtrlClient\n\terr := gtwapi.AddToScheme(client.Scheme())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlabelSelector := util.LabelStrToSelector(a.LabelSelector)\n\tif err := client.List(a.Context, routeList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\t// Find all unhealthy gateway Classes\n\tfor _, route := range routeList.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check if Gateways exists in the same or designated namespace\n\t\t// TODO: when meshes and ClusterIp options are adopted we can add more checks\n\t\t// e.g Service Port matching\n\t\tfor _, gtwref := range route.Spec.ParentRefs {\n\t\t\tnamespace := route.Namespace\n\t\t\tif gtwref.Namespace != nil {\n\t\t\t\tnamespace = string(*gtwref.Namespace)\n\t\t\t}\n\t\t\terr := client.Get(a.Context, ctrl.ObjectKey{Namespace: namespace, Name: string(gtwref.Name)}, gtw, &ctrl.GetOptions{})\n\t\t\tif errors.IsNotFound(err) {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\"HTTPRoute uses the Gateway '%s/%s' which does not exist in the same namespace.\",\n\t\t\t\t\t\tnamespace,\n\t\t\t\t\t\tgtwref.Name,\n\t\t\t\t\t),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: gtw.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: gtw.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t// Check if the aforementioned Gateway allows the HTTPRoutes from the route's namespace\n\t\t\t\tfor _, listener := range gtw.Spec.Listeners {\n\t\t\t\t\tif listener.AllowedRoutes.Namespaces != nil {\n\t\t\t\t\t\tswitch allow := listener.AllowedRoutes.Namespaces.From; {\n\t\t\t\t\t\tcase *allow == gtwapi.NamespacesFromSame:\n\t\t\t\t\t\t\t// check if Gateway is in the same namespace\n\t\t\t\t\t\t\tif route.Namespace != gtw.Namespace {\n\t\t\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\t\t\tText: fmt.Sprintf(\"HTTPRoute '%s/%s' is deployed in a different namespace from Gateway '%s/%s' which only allows HTTPRoutes from its namespace.\",\n\t\t\t\t\t\t\t\t\t\troute.Namespace,\n\t\t\t\t\t\t\t\t\t\troute.Name,\n\t\t\t\t\t\t\t\t\t\tgtw.Namespace,\n\t\t\t\t\t\t\t\t\t\tgtw.Name,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tUnmasked: route.Namespace,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(route.Namespace),\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\tUnmasked: route.Name,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(route.Name),\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\tUnmasked: gtw.Namespace,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Namespace),\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\tUnmasked: gtw.Name,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Name),\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\tcase *allow == gtwapi.NamespacesFromSelector:\n\t\t\t\t\t\t\t// check if our route include the same selector Label\n\t\t\t\t\t\t\tif !util.LabelsIncludeAny(listener.AllowedRoutes.Namespaces.Selector.MatchLabels, route.Labels) {\n\t\t\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\t\t\t\t\"HTTPRoute '%s/%s' can't be attached on Gateway '%s/%s', selector labels do not match HTTProute's labels.\",\n\t\t\t\t\t\t\t\t\t\troute.Namespace,\n\t\t\t\t\t\t\t\t\t\troute.Name,\n\t\t\t\t\t\t\t\t\t\tgtw.Namespace,\n\t\t\t\t\t\t\t\t\t\tgtw.Name,\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tUnmasked: route.Namespace,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(route.Namespace),\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\tUnmasked: route.Name,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(route.Name),\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\tUnmasked: gtw.Namespace,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Namespace),\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\tUnmasked: gtw.Name,\n\t\t\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(gtw.Name),\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\n\t\t\t\t\t\t\t}\n\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}\n\t\t// Check if the Backends are valid services and ports are matching with services Ports\n\t\tfor _, rule := range route.Spec.Rules {\n\t\t\tfor _, backend := range rule.BackendRefs {\n\t\t\t\terr := client.Get(a.Context, ctrl.ObjectKey{Namespace: route.Namespace, Name: string(backend.Name)}, service, &ctrl.GetOptions{})\n\t\t\t\tif errors.IsNotFound(err) {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\t\"HTTPRoute uses the Service '%s/%s' which does not exist.\",\n\t\t\t\t\t\t\troute.Namespace,\n\t\t\t\t\t\t\tbackend.Name,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: service.Namespace,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(service.Namespace),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: service.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(service.Name),\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} else {\n\t\t\t\t\tportMatch := false\n\t\t\t\t\tfor _, svcPort := range service.Spec.Ports {\n\t\t\t\t\t\tif int32(*backend.Port) == svcPort.Port {\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\tif !portMatch {\n\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\t\t\"HTTPRoute's backend service '%s' is using port '%d' but the corresponding K8s service '%s/%s' isn't configured with the same port.\",\n\t\t\t\t\t\t\t\tbackend.Name,\n\t\t\t\t\t\t\t\tint32(*backend.Port),\n\t\t\t\t\t\t\t\tservice.Namespace,\n\t\t\t\t\t\t\t\tservice.Name,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tUnmasked: string(backend.Name),\n\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(string(backend.Name)),\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\tUnmasked: service.Name,\n\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(service.Name),\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\tUnmasked: service.Namespace,\n\t\t\t\t\t\t\t\t\tMasked:   service.Namespace,\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\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", route.Namespace, route.Name)] = common.PreAnalysis{\n\t\t\t\tHTTPRoute:      route,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, route.Name, route.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\treturn a.Results, nil\n\n}\n"
  },
  {
    "path": "pkg/analyzer/httproute_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapiextensionsv1 \"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\"\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\"k8s.io/client-go/kubernetes/scheme\"\n\tfakeclient \"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\nfunc BuildRouteGateway(namespace, name, fromNamespaceref string) gtwapi.Gateway {\n\trouteNamespace := &gtwapi.RouteNamespaces{}\n\tswitch fromNamespaceref {\n\tcase \"Same\":\n\t\tfromSame := gtwapi.NamespacesFromSame\n\t\trouteNamespace.From = &fromSame\n\tcase \"Selector\":\n\t\tfromSelector := gtwapi.NamespacesFromSelector\n\t\trouteNamespace.From = &fromSelector\n\t\trouteNamespace.Selector = &metav1.LabelSelector{}\n\t\trouteNamespace.Selector.MatchLabels = map[string]string{\"foo\": \"bar\"}\n\n\tdefault:\n\t\tfromAll := gtwapi.NamespacesFromAll\n\t\trouteNamespace.From = &fromAll\n\t}\n\tGateway := gtwapi.Gateway{}\n\tGateway.Name = name\n\tGateway.Namespace = namespace\n\tGateway.Spec.GatewayClassName = \"fooclassName\"\n\tGateway.Spec.Listeners = []gtwapi.Listener{\n\t\t{\n\t\t\tName:     \"proxy\",\n\t\t\tPort:     80,\n\t\t\tProtocol: gtwapi.HTTPProtocolType,\n\t\t\tAllowedRoutes: &gtwapi.AllowedRoutes{\n\t\t\t\tNamespaces: routeNamespace,\n\t\t\t},\n\t\t},\n\t}\n\tCondition := metav1.Condition{\n\t\tType:    \"Accepted\",\n\t\tStatus:  \"True\",\n\t\tMessage: \"An expected message\",\n\t\tReason:  \"Test\",\n\t}\n\tGateway.Status.Conditions = []metav1.Condition{Condition}\n\n\treturn Gateway\n}\n\nfunc BuildHTTPRoute(backendName, gtwName gtwapi.ObjectName, gtwNamespace gtwapi.Namespace, svcPort *gtwapi.PortNumber, namespace string) gtwapi.HTTPRoute {\n\tHTTPRoute := gtwapi.HTTPRoute{}\n\tHTTPRoute.Name = \"foohttproute\"\n\tHTTPRoute.Namespace = namespace\n\tHTTPRoute.Spec.ParentRefs = []gtwapi.ParentReference{\n\t\t{\n\t\t\tName:      gtwName,\n\t\t\tNamespace: &gtwNamespace,\n\t\t},\n\t}\n\tHTTPRoute.Spec.Rules = []gtwapi.HTTPRouteRule{\n\t\t{\n\t\t\tBackendRefs: []gtwapi.HTTPBackendRef{\n\t\t\t\t{\n\t\t\t\t\tBackendRef: gtwapi.BackendRef{\n\t\t\t\t\t\tBackendObjectReference: gtwapi.BackendObjectReference{\n\t\t\t\t\t\t\tName: backendName,\n\t\t\t\t\t\t\tPort: svcPort,\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\treturn HTTPRoute\n}\n\n/*\n\tTesting different cases\n\n1. Gateway doesn't exist or at least doesn't exist in the same namespace\n2. Gateway exists in different namespace, is configured in httproute's spec\nand Gateway's configuration is allowing only from its same namespace\n3. Gateway exists in the same namespace but has selectors different from route's labels\n4. BackendRef is pointing to a non existent Service\n5. BackendRef's port and Service Port are different\n*/\nfunc TestGWMissiningHTTRouteAnalyzer(t *testing.T) {\n\tbackendName := gtwapi.ObjectName(\"foobackend\")\n\tgtwName := gtwapi.ObjectName(\"non-existent\")\n\tgtwNamespace := gtwapi.Namespace(\"non-existent\")\n\tsvcPort := gtwapi.PortNumber(1027)\n\thttpRouteNamespace := \"default\"\n\n\tHTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&HTTPRoute,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := HTTPRouteAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"HTTPRoute uses the Gateway 'non-existent/non-existent' which does not exist in the same namespace.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%s> , not found in HTTPRoute's analysis results\", want)\n\t}\n\n}\n\nfunc TestGWConfigSameHTTRouteAnalyzer(t *testing.T) {\n\tbackendName := gtwapi.ObjectName(\"foobackend\")\n\tgtwName := gtwapi.ObjectName(\"gatewayname\")\n\tgtwNamespace := gtwapi.Namespace(\"differentnamespace\")\n\tsvcPort := gtwapi.PortNumber(1027)\n\thttpRouteNamespace := \"default\"\n\n\tHTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)\n\n\tGateway := BuildRouteGateway(\"differentnamespace\", \"gatewayname\", \"Same\")\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&HTTPRoute,\n\t\t&Gateway,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := HTTPRouteAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"HTTPRoute 'default/foohttproute' is deployed in a different namespace from Gateway 'differentnamespace/gatewayname' which only allows HTTPRoutes from its namespace.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%s> , not found in HTTPRoute's analysis results\", want)\n\t}\n}\nfunc TestGWConfigSelectorHTTRouteAnalyzer(t *testing.T) {\n\tbackendName := gtwapi.ObjectName(\"foobackend\")\n\tgtwName := gtwapi.ObjectName(\"gatewayname\")\n\tgtwNamespace := gtwapi.Namespace(\"default\")\n\tsvcPort := gtwapi.PortNumber(1027)\n\thttpRouteNamespace := \"default\"\n\n\tHTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)\n\n\tGateway := BuildRouteGateway(\"default\", \"gatewayname\", \"Selector\")\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&HTTPRoute,\n\t\t&Gateway,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := HTTPRouteAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"HTTPRoute 'default/foohttproute' can't be attached on Gateway 'default/gatewayname', selector labels do not match HTTProute's labels.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%s> , not found in HTTPRoute's analysis results\", want)\n\t}\n}\n\nfunc TestSvcMissingHTTRouteAnalyzer(t *testing.T) {\n\tbackendName := gtwapi.ObjectName(\"foobackend\")\n\tgtwName := gtwapi.ObjectName(\"gatewayname\")\n\tgtwNamespace := gtwapi.Namespace(\"default\")\n\tsvcPort := gtwapi.PortNumber(1027)\n\thttpRouteNamespace := \"default\"\n\n\tHTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)\n\n\tGateway := BuildRouteGateway(\"default\", \"gatewayname\", \"Same\")\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&HTTPRoute,\n\t\t&Gateway,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := HTTPRouteAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"HTTPRoute uses the Service 'default/foobackend' which does not exist.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%s> , not found in HTTPRoute's analysis results\", want)\n\t}\n}\nfunc TestSvcDifferentPortHTTRouteAnalyzer(t *testing.T) {\n\t//Add a Service Object\n\tService := corev1.Service{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"foobackend\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: corev1.ServiceSpec{\n\t\t\tSelector: map[string]string{\n\t\t\t\t\"app\": \"example-app\",\n\t\t\t},\n\t\t\tPorts: []corev1.ServicePort{\n\t\t\t\t{\n\t\t\t\t\tName:       \"http\",\n\t\t\t\t\tProtocol:   \"TCP\",\n\t\t\t\t\tPort:       80,\n\t\t\t\t\tTargetPort: intstr.FromInt(8080),\n\t\t\t\t},\n\t\t\t},\n\t\t\tType: corev1.ServiceTypeClusterIP,\n\t\t},\n\t}\n\tbackendName := gtwapi.ObjectName(\"foobackend\")\n\tgtwName := gtwapi.ObjectName(\"gatewayname\")\n\tgtwNamespace := gtwapi.Namespace(\"default\")\n\t// different port\n\tsvcPort := gtwapi.PortNumber(1027)\n\thttpRouteNamespace := \"default\"\n\n\tHTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)\n\n\tGateway := BuildRouteGateway(\"default\", \"gatewayname\", \"Same\")\n\t// Create a Gateway Analyzer instance with the fake client\n\tscheme := scheme.Scheme\n\terr := gtwapi.Install(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\terr = apiextensionsv1.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tobjects := []runtime.Object{\n\t\t&HTTPRoute,\n\t\t&Gateway,\n\t\t&Service,\n\t}\n\n\tfakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()\n\n\tanalyzerInstance := HTTPRouteAnalyzer{}\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: fakeClient,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := analyzerInstance.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"HTTPRoute's backend service 'foobackend' is using port '1027' but the corresponding K8s service 'default/foobackend' isn't configured with the same port.\"\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !errorFound {\n\t\tt.Errorf(\"Expected message, <%s> , not found in HTTPRoute's analysis results\", want)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/ingress.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype IngressAnalyzer struct{}\n\nfunc (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Ingress\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"networking\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tlist, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, ing := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// get ingressClassName\n\t\tingressClassName := ing.Spec.IngressClassName\n\t\tif ingressClassName == nil {\n\t\t\tingClassValue := ing.Annotations[\"kubernetes.io/ingress.class\"]\n\t\t\tif ingClassValue == \"\" {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.ingressClassName\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Ingress %s/%s does not specify an Ingress class.\", ing.Namespace, ing.Name),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: ing.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(ing.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: ing.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(ing.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tingressClassName = &ingClassValue\n\t\t\t}\n\t\t}\n\n\t\t// check if ingressclass exist\n\t\tif ingressClassName != nil {\n\t\t\t_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.ingressClassName\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Ingress uses the ingress class %s which does not exist.\", *ingressClassName),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: *ingressClassName,\n\t\t\t\t\t\t\tMasked:   util.MaskString(*ingressClassName),\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\t// loop over rules\n\t\tfor _, rule := range ing.Spec.Rules {\n\t\t\t// loop over HTTP paths\n\t\t\tif rule.HTTP != nil {\n\t\t\t\tfor _, path := range rule.HTTP.Paths {\n\t\t\t\t\t_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.rules.http.paths.backend.service\")\n\n\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\tText:          fmt.Sprintf(\"Ingress uses the service %s/%s which does not exist.\", ing.Namespace, path.Backend.Service.Name),\n\t\t\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tUnmasked: ing.Namespace,\n\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(ing.Namespace),\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\tUnmasked: path.Backend.Service.Name,\n\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(path.Backend.Service.Name),\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\tfor _, tls := range ing.Spec.TLS {\n\t\t\t_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.tls.secretName\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Ingress uses the secret %s/%s as a TLS certificate which does not exist.\", ing.Namespace, tls.SecretName),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: ing.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(ing.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: tls.SecretName,\n\t\t\t\t\t\t\tMasked:   util.MaskString(tls.SecretName),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", ing.Namespace, ing.Name)] = common.PreAnalysis{\n\t\t\t\tIngress:        ing,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, ing.Name, ing.Namespace).Set(float64(len(failures)))\n\n\t\t}\n\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Ingress.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/ingress_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestIngressAnalyzer(t *testing.T) {\n\t// Create test cases\n\ttestCases := []struct {\n\t\tname           string\n\t\tingress        *networkingv1.Ingress\n\t\texpectedIssues []string\n\t}{\n\t\t{\n\t\t\tname: \"Non-existent backend service\",\n\t\t\tingress: &networkingv1.Ingress{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"test-ingress\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: networkingv1.IngressSpec{\n\t\t\t\t\tRules: []networkingv1.IngressRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHost: \"example.com\",\n\t\t\t\t\t\t\tIngressRuleValue: networkingv1.IngressRuleValue{\n\t\t\t\t\t\t\t\tHTTP: &networkingv1.HTTPIngressRuleValue{\n\t\t\t\t\t\t\t\t\tPaths: []networkingv1.HTTPIngressPath{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tPath: \"/\",\n\t\t\t\t\t\t\t\t\t\t\tBackend: networkingv1.IngressBackend{\n\t\t\t\t\t\t\t\t\t\t\t\tService: &networkingv1.IngressServiceBackend{\n\t\t\t\t\t\t\t\t\t\t\t\t\tName: \"non-existent-service\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tPort: networkingv1.ServiceBackendPort{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNumber: 80,\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\texpectedIssues: []string{\n\t\t\t\t\"Ingress default/test-ingress does not specify an Ingress class.\",\n\t\t\t\t\"Ingress uses the service default/non-existent-service which does not exist.\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Non-existent TLS secret\",\n\t\t\tingress: &networkingv1.Ingress{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"test-ingress-tls\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: networkingv1.IngressSpec{\n\t\t\t\t\tTLS: []networkingv1.IngressTLS{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHosts:      []string{\"example.com\"},\n\t\t\t\t\t\t\tSecretName: \"non-existent-secret\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tRules: []networkingv1.IngressRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHost: \"example.com\",\n\t\t\t\t\t\t\tIngressRuleValue: networkingv1.IngressRuleValue{\n\t\t\t\t\t\t\t\tHTTP: &networkingv1.HTTPIngressRuleValue{\n\t\t\t\t\t\t\t\t\tPaths: []networkingv1.HTTPIngressPath{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tPath: \"/\",\n\t\t\t\t\t\t\t\t\t\t\tBackend: networkingv1.IngressBackend{\n\t\t\t\t\t\t\t\t\t\t\t\tService: &networkingv1.IngressServiceBackend{\n\t\t\t\t\t\t\t\t\t\t\t\t\tName: \"test-service\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tPort: networkingv1.ServiceBackendPort{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNumber: 80,\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\texpectedIssues: []string{\n\t\t\t\t\"Ingress default/test-ingress-tls does not specify an Ingress class.\",\n\t\t\t\t\"Ingress uses the service default/test-service which does not exist.\",\n\t\t\t\t\"Ingress uses the secret default/non-existent-secret as a TLS certificate which does not exist.\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple issues\",\n\t\t\tingress: &networkingv1.Ingress{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"test-ingress-multi\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: networkingv1.IngressSpec{\n\t\t\t\t\tTLS: []networkingv1.IngressTLS{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHosts:      []string{\"example.com\"},\n\t\t\t\t\t\t\tSecretName: \"non-existent-secret\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tRules: []networkingv1.IngressRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tHost: \"example.com\",\n\t\t\t\t\t\t\tIngressRuleValue: networkingv1.IngressRuleValue{\n\t\t\t\t\t\t\t\tHTTP: &networkingv1.HTTPIngressRuleValue{\n\t\t\t\t\t\t\t\t\tPaths: []networkingv1.HTTPIngressPath{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tPath: \"/\",\n\t\t\t\t\t\t\t\t\t\t\tBackend: networkingv1.IngressBackend{\n\t\t\t\t\t\t\t\t\t\t\t\tService: &networkingv1.IngressServiceBackend{\n\t\t\t\t\t\t\t\t\t\t\t\t\tName: \"non-existent-service\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tPort: networkingv1.ServiceBackendPort{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tNumber: 80,\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\texpectedIssues: []string{\n\t\t\t\t\"Ingress default/test-ingress-multi does not specify an Ingress class.\",\n\t\t\t\t\"Ingress uses the service default/non-existent-service which does not exist.\",\n\t\t\t\t\"Ingress uses the secret default/non-existent-secret as a TLS certificate which does not exist.\",\n\t\t\t},\n\t\t},\n\t}\n\n\t// Run test cases\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Create a new context and clientset for each test case\n\t\t\tctx := context.Background()\n\t\t\tclientset := fake.NewSimpleClientset()\n\n\t\t\t// Create the ingress in the fake clientset\n\t\t\t_, err := clientset.NetworkingV1().Ingresses(tc.ingress.Namespace).Create(ctx, tc.ingress, metav1.CreateOptions{})\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Create the analyzer configuration\n\t\t\tconfig := common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: clientset,\n\t\t\t\t},\n\t\t\t\tContext:   ctx,\n\t\t\t\tNamespace: tc.ingress.Namespace,\n\t\t\t}\n\n\t\t\t// Create the analyzer and run analysis\n\t\t\tanalyzer := IngressAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(config)\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Check that we got the expected number of issues\n\t\t\tassert.Len(t, results, 1, \"Expected 1 result\")\n\t\t\tresult := results[0]\n\t\t\tassert.Len(t, result.Error, len(tc.expectedIssues), \"Expected %d issues, got %d\", len(tc.expectedIssues), len(result.Error))\n\n\t\t\t// Check that each expected issue is present\n\t\t\tfor _, expectedIssue := range tc.expectedIssues {\n\t\t\t\tfound := false\n\t\t\t\tfor _, failure := range result.Error {\n\t\t\t\t\tif failure.Text == expectedIssue {\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\tassert.True(t, found, \"Expected to find issue: %s\", expectedIssue)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIngressAnalyzerLabelSelector(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset(\n\t\t&networkingv1.Ingress{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ingress-with-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: networkingv1.IngressSpec{\n\t\t\t\t// Missing ingress class to trigger a failure\n\t\t\t},\n\t\t},\n\t\t&networkingv1.Ingress{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"ingress-without-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: networkingv1.IngressSpec{\n\t\t\t\t// Missing ingress class to trigger a failure\n\t\t\t},\n\t\t},\n\t)\n\n\t// Test with label selector\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=test\",\n\t}\n\n\tanalyzer := IngressAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/ingress-with-label\", results[0].Name)\n}\n\n// Helper functions\nfunc strPtr(s string) *string {\n\treturn &s\n}\n\nfunc pathTypePtr(p networkingv1.PathType) *networkingv1.PathType {\n\treturn &p\n}\n"
  },
  {
    "path": "pkg/analyzer/installplan_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n)\n\nfunc TestInstallPlanAnalyzer(t *testing.T) {\n\tok := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"InstallPlan\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"ip-ok\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\"phase\": \"Complete\"},\n\t\t},\n\t}\n\n\tbad := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"InstallPlan\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"ip-bad\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"phase\": \"Failed\",\n\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"reason\":  \"ExecutionError\",\n\t\t\t\t\t\t\"message\": \"something went wrong\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"installplans\"}: \"InstallPlanList\",\n\t}\n\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, ok, bad)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (InstallPlanAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\n\tif len(res) != 1 {\n\t\tt.Fatalf(\"expected 1 result, got %d\", len(res))\n\t}\n\tif res[0].Kind != \"InstallPlan\" || !strings.Contains(res[0].Name, \"ns1/ip-bad\") {\n\t\tt.Fatalf(\"unexpected result: %#v\", res[0])\n\t}\n\tif len(res[0].Error) == 0 || !strings.Contains(res[0].Error[0].Text, \"ExecutionError\") {\n\t\tt.Fatalf(\"expected 'ExecutionError' in failure, got %#v\", res[0].Error)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/instalplan.go",
    "content": "package analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\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\ntype InstallPlanAnalyzer struct{}\n\nvar ipGVR = schema.GroupVersionResource{\n\tGroup: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"installplans\",\n}\n\nfunc (InstallPlanAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"InstallPlan\"\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in %s analyzer\", kind)\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().\n\t\tResource(ipGVR).Namespace(metav1.NamespaceAll).\n\t\tList(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar results []common.Result\n\tfor _, item := range list.Items {\n\t\tns, name := item.GetNamespace(), item.GetName()\n\t\tphase, _, _ := unstructured.NestedString(item.Object, \"status\", \"phase\")\n\n\t\tvar failures []common.Failure\n\t\tif phase != \"\" && phase != \"Complete\" {\n\t\t\treason := firstCondStr(&item, \"reason\")\n\t\t\tmsg := firstCondStr(&item, \"message\")\n\t\t\tswitch {\n\t\t\tcase reason != \"\" && msg != \"\":\n\t\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"phase=%q: %s: %s\", phase, reason, msg)})\n\t\t\tcase reason != \"\" || msg != \"\":\n\t\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"phase=%q: %s%s\", phase, reason, msg)})\n\t\t\tdefault:\n\t\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"phase=%q (approval/manual? check status.conditions)\", phase)})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  kind,\n\t\t\t\tName:  ns + \"/\" + name,\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc firstCondStr(u *unstructured.Unstructured, field string) string {\n\tconds, _, _ := unstructured.NestedSlice(u.Object, \"status\", \"conditions\")\n\tif len(conds) == 0 {\n\t\treturn \"\"\n\t}\n\tm, _ := conds[0].(map[string]any)\n\tif m == nil {\n\t\treturn \"\"\n\t}\n\tv, _ := m[field].(string)\n\treturn v\n}\n"
  },
  {
    "path": "pkg/analyzer/job.go",
    "content": "/*\nCopyright 2025 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype JobAnalyzer struct{}\n\nfunc (analyzer JobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Job\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"batch\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tJobList, err := a.Client.GetClient().BatchV1().Jobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, Job := range JobList.Items {\n\t\tvar failures []common.Failure\n\t\tif Job.Spec.Suspend != nil && *Job.Spec.Suspend {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.suspend\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"Job %s is suspended\", Job.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: Job.Namespace,\n\t\t\t\t\t\tMasked:   util.MaskString(Job.Namespace),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: Job.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(Job.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\tif Job.Status.Failed > 0 {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"status.failed\")\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"Job %s has failed\", Job.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: Job.Namespace,\n\t\t\t\t\t\tMasked:   util.MaskString(Job.Namespace),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: Job.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(Job.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", Job.Namespace, Job.Name)] = common.PreAnalysis{\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, Job.Name, Job.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tcurrentAnalysis := common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/job_test.go",
    "content": "/*\nCopyright 2025 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tbatchv1 \"k8s.io/api/batch/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestJobAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       common.Analyzer\n\t\texpectations []struct {\n\t\t\tname          string\n\t\t\tfailuresCount int\n\t\t}\n\t}{\n\t\t{\n\t\t\tname: \"Suspended Job\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.Job{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"suspended-job\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\t\tSuspend: boolPtr(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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/suspended-job\",\n\t\t\t\t\tfailuresCount: 1, // One failure for being suspended\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tname: \"Failed Job\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.Job{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"failed-job\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{},\n\t\t\t\t\t\t\tStatus: batchv1.JobStatus{\n\t\t\t\t\t\t\t\tFailed: 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/failed-job\",\n\t\t\t\t\tfailuresCount: 1, // One failure for failed job\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Valid Job\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.Job{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"valid-job\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{},\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t// No expectations for valid job\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple issues\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&batchv1.Job{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"multiple-issues\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: batchv1.JobSpec{\n\t\t\t\t\t\t\t\tSuspend: boolPtr(true),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: batchv1.JobStatus{\n\t\t\t\t\t\t\t\tFailed: 1,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/multiple-issues\",\n\t\t\t\t\tfailuresCount: 2, // Two failures: suspended and failed job\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tanalyzer := JobAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(tt.config)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, results, len(tt.expectations))\n\n\t\t\t// Sort results by name for consistent comparison\n\t\t\tsort.Slice(results, func(i, j int) bool {\n\t\t\t\treturn results[i].Name < results[j].Name\n\t\t\t})\n\n\t\t\tfor i, expectation := range tt.expectations {\n\t\t\t\trequire.Equal(t, expectation.name, results[i].Name)\n\t\t\t\trequire.Len(t, results[i].Error, expectation.failuresCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestJobAnalyzerLabelSelector(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset(\n\t\t&batchv1.Job{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"job-with-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: batchv1.JobSpec{},\n\t\t\tStatus: batchv1.JobStatus{\n\t\t\t\tFailed: 1,\n\t\t\t},\n\t\t},\n\t\t&batchv1.Job{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"job-without-label\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: batchv1.JobSpec{},\n\t\t},\n\t)\n\n\t// Test with label selector\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=test\",\n\t}\n\n\tanalyzer := JobAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/job-with-label\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/log.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nvar (\n\terrorPattern = regexp.MustCompile(`(error|exception|fail)`)\n\ttailLines    = int64(100)\n)\n\ntype LogAnalyzer struct {\n}\n\nfunc (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Log\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// search all namespaces for pods that are not running\n\tlist, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\t// Iterate through each pod\n\n\tfor _, pod := range list.Items {\n\t\tpodName := pod.Name\n\t\tfor _, c := range pod.Spec.Containers {\n\t\t\tvar failures []common.Failure\n\t\t\tpodLogOptions := v1.PodLogOptions{\n\t\t\t\tTailLines: &tailLines,\n\t\t\t\tContainer: c.Name,\n\t\t\t}\n\t\t\tpodLogs, err := a.Client.Client.CoreV1().Pods(pod.Namespace).GetLogs(podName, &podLogOptions).DoRaw(a.Context)\n\t\t\tif err != nil {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText: fmt.Sprintf(\"Error %s from Pod %s\", err.Error(), pod.Name),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: pod.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\trawlogs := string(podLogs)\n\t\t\t\tif errorPattern.MatchString(strings.ToLower(rawlogs)) {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText: printErrorLines(rawlogs, errorPattern),\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: pod.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Name),\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\tif len(failures) > 0 {\n\t\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s/%s\", pod.Namespace, pod.Name, c.Name)] = common.PreAnalysis{\n\t\t\t\t\tFailureDetails: failures,\n\t\t\t\t\tPod:            pod,\n\t\t\t\t}\n\t\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures)))\n\t\t\t}\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tcurrentAnalysis := common.Result{\n\t\t\tKind:  \"Pod\",\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\tparent, found := util.GetParent(a.Client, value.Pod.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\nfunc printErrorLines(logs string, errorPattern *regexp.Regexp) string {\n\t// Split the logs into lines\n\tlogLines := strings.Split(logs, \"\\n\")\n\n\t// Check each line for errors and print the lines containing errors\n\tfor _, line := range logLines {\n\t\tif errorPattern.MatchString(strings.ToLower(line)) {\n\t\t\treturn line\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/analyzer/log_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestLogAnalyzer(t *testing.T) {\n\toldPattern := errorPattern\n\terrorPattern = regexp.MustCompile(`(fake logs)`)\n\tt.Cleanup(func() {\n\t\terrorPattern = oldPattern\n\t})\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"Name\":      \"Pod1\",\n\t\t\t\t\t\t\t\"Namespace\": \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"test-container1\",\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: \"test-container2\",\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&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"Name\":      \"Pod1\",\n\t\t\t\t\t\t\t\"Namespace\": \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod3\",\n\t\t\t\t\t\tNamespace: \"test-namespace\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"Name\":      \"Pod1\",\n\t\t\t\t\t\t\t\"Namespace\": \"test-namespace\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod4\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"Name\":      \"Pod4\",\n\t\t\t\t\t\t\t\"Namespace\": \"default\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"test-container3\",\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\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tlogAnalyzer := LogAnalyzer{}\n\tresults, err := logAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\n\tsort.Slice(results, func(i, j int) bool {\n\t\treturn results[i].Name < results[j].Name\n\t})\n\n\texpectations := []string{\"default/Pod1/test-container1\", \"default/Pod1/test-container2\", \"default/Pod4/test-container3\"}\n\n\tfor i, expectation := range expectations {\n\t\trequire.Equal(t, expectation, results[i].Name)\n\n\t\tfor _, failure := range results[i].Error {\n\t\t\trequire.Equal(t, \"fake logs\", failure.Text)\n\t\t}\n\t}\n}\n\nfunc TestLogAnalyzerLabelSelectorFiltering(t *testing.T) {\n\toldPattern := errorPattern\n\terrorPattern = regexp.MustCompile(`(fake logs)`)\n\tt.Cleanup(func() {\n\t\terrorPattern = oldPattern\n\t})\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"log\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"test-container1\",\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&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"test-container2\",\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\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=log\",\n\t}\n\n\tlogAnalyzer := LogAnalyzer{}\n\tresults, err := logAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/Pod1/test-container1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/mutating_webhook.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype MutatingWebhookAnalyzer struct{}\n\nfunc (MutatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"MutatingWebhookConfiguration\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"apps\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tmutatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, webhookConfig := range mutatingWebhooks.Items {\n\t\tfor _, webhook := range webhookConfig.Webhooks {\n\t\t\tvar failures []common.Failure\n\n\t\t\tif webhook.ClientConfig.Service == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsvc := webhook.ClientConfig.Service\n\t\t\t// Get the service\n\t\t\tservice, err := a.Client.GetClient().CoreV1().Services(svc.Namespace).Get(context.Background(), svc.Name, v1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\t// If the service is not found, we can't check the pods\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Service %s not found as mapped to by Mutating Webhook %s\", svc.Name, webhook.Name),\n\t\t\t\t\tKubernetesDoc: apiDoc.GetApiDocV2(\"spec.webhook.clientConfig.service\"),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: svc.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(svc.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", webhookConfig.Namespace, webhook.Name)] = common.PreAnalysis{\n\t\t\t\t\tMutatingWebhook: webhookConfig,\n\t\t\t\t\tFailureDetails:  failures,\n\t\t\t\t}\n\t\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, webhook.Name, webhookConfig.Namespace).Set(float64(len(failures)))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// When Service selectors are empty we defer to service analyser\n\t\t\tif len(service.Spec.Selector) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Get pods within service\n\t\t\tpods, err := a.Client.GetClient().CoreV1().Pods(svc.Namespace).List(context.Background(), v1.ListOptions{\n\t\t\t\tLabelSelector: util.MapToString(service.Spec.Selector),\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\tif len(pods.Items) == 0 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"No active pods found within service %s as mapped to by Mutating Webhook %s\", svc.Name, webhook.Name),\n\t\t\t\t\tKubernetesDoc: apiDoc.GetApiDocV2(\"spec.webhook.clientConfig.service\"),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t}\n\t\t\tfor _, pod := range pods.Items {\n\t\t\t\tif pod.Status.Phase != \"Running\" {\n\t\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.webhook\")\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\t\"Mutating Webhook (%s) is pointing to an inactive receiver pod (%s)\",\n\t\t\t\t\t\t\twebhook.Name,\n\t\t\t\t\t\t\tpod.Name,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: webhook.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(webhook.Name),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: pod.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Name),\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\tif len(failures) > 0 {\n\t\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", webhookConfig.Namespace, webhook.Name)] = common.PreAnalysis{\n\t\t\t\t\tMutatingWebhook: webhookConfig,\n\t\t\t\t\tFailureDetails:  failures,\n\t\t\t\t}\n\t\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, webhook.Name, webhookConfig.Namespace).Set(float64(len(failures)))\n\t\t\t}\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.MutatingWebhook.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/mutating_webhook_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tadmissionregistrationv1 \"k8s.io/api/admissionregistration/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestMutatingWebhookAnalyzer(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service2\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t// No such pod exists in the test namespace\n\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod2\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service3\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t// len(service.Spec.Selector) == 0\n\t\t\t\t\t\tSelector: map[string]string{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&admissionregistrationv1.MutatingWebhookConfiguration{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-mutating-webhook-config\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: []admissionregistrationv1.MutatingWebhook{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Failure: Pointing to an inactive receiver pod\n\t\t\t\t\t\t\tName: \"webhook1\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\t\t\t\tNamespace: \"default\",\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\t{\n\t\t\t\t\t\t\t// Failure: No active pods found in the test namespace\n\t\t\t\t\t\t\tName: \"webhook2\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service2\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\tName: \"webhook3\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service3\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\t// Failure: Service doesn't exist.\n\t\t\t\t\t\t\tName: \"webhook4\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service4-doesn't-exist\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\t// Service is nil.\n\t\t\t\t\t\t\tName:         \"webhook5\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{},\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\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tmwAnalyzer := MutatingWebhookAnalyzer{}\n\tresults, err := mwAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\n\t// The results should contain: webhook1, webhook2, and webhook4\n\tresultsLen := 3\n\trequire.Equal(t, resultsLen, len(results))\n}\n\nfunc TestMutatingWebhookAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"mutating-webhook\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"mutating-webhook\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&admissionregistrationv1.MutatingWebhookConfiguration{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-mutating-webhook-config\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"mutating-webhook\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: []admissionregistrationv1.MutatingWebhook{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"webhook1\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\t\t\t\tNamespace: \"default\",\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\t&admissionregistrationv1.MutatingWebhookConfiguration{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-mutating-webhook-config2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: []admissionregistrationv1.MutatingWebhook{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tName: \"webhook2\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\t\t\t\tNamespace: \"default\",\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\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=mutating-webhook\",\n\t}\n\n\tmwAnalyzer := MutatingWebhookAnalyzer{}\n\tresults, err := mwAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/webhook1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/netpol.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype NetworkPolicyAnalyzer struct{}\n\nfunc (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"NetworkPolicy\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"networking\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// get all network policies in the namespace\n\tpolicies, err := a.Client.GetClient().NetworkingV1().\n\t\tNetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, policy := range policies.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check if policy allows traffic to all pods in the namespace\n\t\tif len(policy.Spec.PodSelector.MatchLabels) == 0 {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.podSelector.matchLabels\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"Network policy allows traffic to all pods: %s\", policy.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: policy.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(policy.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\t// Check if policy is not applied to any pods\n\t\t\tpodList, err := util.GetPodListByLabels(a.Client.GetClient(), a.Namespace, policy.Spec.PodSelector.MatchLabels)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(podList.Items) == 0 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText: fmt.Sprintf(\"Network policy is not applied to any pods: %s\", policy.Name),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: policy.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(policy.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", policy.Namespace, policy.Name)] = common.PreAnalysis{\n\t\t\t\tFailureDetails: failures,\n\t\t\t\tNetworkPolicy:  policy,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, policy.Name, policy.Namespace).Set(float64(len(failures)))\n\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tcurrentAnalysis := common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/netpol_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/magiconair/properties/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestNetpolNoPods(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\"app\": \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tIngress: []networkingv1.NetworkPolicyIngressRule{\n\t\t\t\t{\n\t\t\t\t\tFrom: []networkingv1.NetworkPolicyPeer{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPodSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"database\",\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\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tanalyzer := NetworkPolicyAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, len(results), 1)\n\tassert.Equal(t, results[0].Kind, \"NetworkPolicy\")\n\n}\n\nfunc TestNetpolWithPod(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"default\",\n\t\t},\n\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\"app\": \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tIngress: []networkingv1.NetworkPolicyIngressRule{\n\t\t\t\t{\n\t\t\t\t\tFrom: []networkingv1.NetworkPolicyPeer{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPodSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"database\",\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}, &v1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName:      \"example\",\n\t\t\tNamespace: \"default\",\n\t\t\tLabels: map[string]string{\n\t\t\t\t\"app\": \"example\",\n\t\t\t},\n\t\t},\n\t\tSpec: v1.PodSpec{\n\t\t\tContainers: []v1.Container{\n\t\t\t\t{\n\t\t\t\t\tName:  \"example\",\n\t\t\t\t\tImage: \"example\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tanalyzer := NetworkPolicyAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, len(results), 0)\n}\n\nfunc TestNetpolNoPodsNamespaceFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&networkingv1.NetworkPolicy{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"policy-without-podselector-match-labels\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\t\t// len(MatchLabels) == 0 should trigger a failure.\n\t\t\t\t\t// Allowing traffic to all pods.\n\t\t\t\t\tMatchLabels: map[string]string{},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&networkingv1.NetworkPolicy{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"example\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tIngress: []networkingv1.NetworkPolicyIngressRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tFrom: []networkingv1.NetworkPolicyPeer{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPodSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\t\"app\": \"database\",\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\t&networkingv1.NetworkPolicy{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"other-namespace\",\n\t\t\t},\n\t\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"example\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tIngress: []networkingv1.NetworkPolicyIngressRule{\n\t\t\t\t\t{\n\t\t\t\t\t\tFrom: []networkingv1.NetworkPolicyPeer{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tPodSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\t\t\"app\": \"database\",\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\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tanalyzer := NetworkPolicyAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, len(results), 2)\n\tassert.Equal(t, results[0].Kind, \"NetworkPolicy\")\n\n}\n\nfunc TestNetpolLabelSelectorFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&networkingv1.NetworkPolicy{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example1\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\": \"netpol\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tSpec: networkingv1.NetworkPolicySpec{\n\t\t\t\tPodSelector: metav1.LabelSelector{\n\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"netpol\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&networkingv1.NetworkPolicy{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t)\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=netpol\",\n\t}\n\n\tanalyzer := NetworkPolicyAnalyzer{}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(results), 1)\n}\n"
  },
  {
    "path": "pkg/analyzer/node.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype NodeAnalyzer struct{}\n\nfunc (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Node\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tlist, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, node := range list.Items {\n\t\tvar failures []common.Failure\n\t\tfor _, nodeCondition := range node.Status.Conditions {\n\t\t\t// https://kubernetes.io/docs/concepts/architecture/nodes/#condition\n\t\t\tswitch nodeCondition.Type {\n\t\t\tcase v1.NodeReady:\n\t\t\t\tif nodeCondition.Status != v1.ConditionTrue {\n\t\t\t\t\tfailures = addNodeConditionFailure(failures, node.Name, nodeCondition)\n\t\t\t\t}\n\t\t\t// k3s `EtcdIsVoter`` should not be reported as an error\n\t\t\tcase v1.NodeConditionType(\"EtcdIsVoter\"):\n\t\t\t\tbreak\n\t\t\tdefault:\n\t\t\t\t// For other conditions:\n\t\t\t\t// - Report True or Unknown status as failures (for standard conditions)\n\t\t\t\t// - Report any unknown condition type as a failure\n\t\t\t\tif nodeCondition.Status == v1.ConditionTrue || nodeCondition.Status == v1.ConditionUnknown || !isKnownNodeConditionType(nodeCondition.Type) {\n\t\t\t\t\tfailures = addNodeConditionFailure(failures, node.Name, nodeCondition)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[node.Name] = common.PreAnalysis{\n\t\t\t\tNode:           node,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, node.Name, \"\").Set(float64(len(failures)))\n\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Node.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, err\n}\n\nfunc addNodeConditionFailure(failures []common.Failure, nodeName string, nodeCondition v1.NodeCondition) []common.Failure {\n\tfailures = append(failures, common.Failure{\n\t\tText: fmt.Sprintf(\"%s has condition of type %s, reason %s: %s\", nodeName, nodeCondition.Type, nodeCondition.Reason, nodeCondition.Message),\n\t\tSensitive: []common.Sensitive{\n\t\t\t{\n\t\t\t\tUnmasked: nodeName,\n\t\t\t\tMasked:   util.MaskString(nodeName),\n\t\t\t},\n\t\t},\n\t})\n\treturn failures\n}\n\n// isKnownNodeConditionType checks if the condition type is a standard Kubernetes node condition\nfunc isKnownNodeConditionType(conditionType v1.NodeConditionType) bool {\n\tswitch conditionType {\n\tcase v1.NodeReady,\n\t\tv1.NodeMemoryPressure,\n\t\tv1.NodeDiskPressure,\n\t\tv1.NodePIDPressure,\n\t\tv1.NodeNetworkUnavailable:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/node_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestNodeAnalyzer(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Node{\n\t\t\t\t\t// A node without Status Conditions shouldn't contribute to failures.\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Node1\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Node{\n\t\t\t\t\t// Nodes are not filtered using namespace.\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Node2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.NodeStatus{\n\t\t\t\t\t\tConditions: []v1.NodeCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Won't contribute to failures.\n\t\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Will contribute to failures.\n\t\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionFalse,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Will contribute to failures.\n\t\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionUnknown,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Non-false statuses for the default cases contribute to failures.\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeMemoryPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeDiskPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodePIDPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeNetworkUnavailable,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionTrue,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeMemoryPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionUnknown,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeDiskPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionUnknown,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodePIDPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionUnknown,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeNetworkUnavailable,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionUnknown,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// A cloud provider may set their own condition and/or a new status\n\t\t\t\t\t\t\t// might be introduced. In such cases a failure is assumed and\n\t\t\t\t\t\t\t// the code shouldn't break, although it might be a false positive.\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   \"UnknownNodeConditionType\",\n\t\t\t\t\t\t\t\tStatus: \"CompletelyUnknown\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// These won't contribute to failures.\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeMemoryPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionFalse,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeDiskPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionFalse,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodePIDPressure,\n\t\t\t\t\t\t\t\tStatus: v1.ConditionFalse,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeNetworkUnavailable,\n\t\t\t\t\t\t\t\tStatus: v1.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\t&v1.Node{\n\t\t\t\t\t// A node without any failures shouldn't be present in the results.\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Node3\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.NodeStatus{\n\t\t\t\t\t\tConditions: []v1.NodeCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Won't contribute to failures.\n\t\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\t\tStatus: v1.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},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"test\",\n\t}\n\n\tnAnalyzer := NodeAnalyzer{}\n\tresults, err := nAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\n\tsort.Slice(results, func(i, j int) bool {\n\t\treturn results[i].Name < results[j].Name\n\t})\n\n\texpectations := []struct {\n\t\tname          string\n\t\tfailuresCount int\n\t}{\n\t\t{\n\t\t\tname:          \"Node2\",\n\t\t\tfailuresCount: 11,\n\t\t},\n\t}\n\n\trequire.Equal(t, len(expectations), len(results))\n\n\tfor i, result := range results {\n\t\trequire.Equal(t, expectations[i].name, result.Name)\n\t\trequire.Equal(t, expectations[i].failuresCount, len(result.Error))\n\t}\n}\n\nfunc TestNodeAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(&v1.Node{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"Node1\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"node\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tStatus: v1.NodeStatus{\n\t\t\t\t\tConditions: []v1.NodeCondition{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\tStatus: v1.ConditionFalse,\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\t&v1.Node{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Node2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.NodeStatus{\n\t\t\t\t\t\tConditions: []v1.NodeCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   v1.NodeReady,\n\t\t\t\t\t\t\t\tStatus: v1.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},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=node\",\n\t}\n\n\tnAnalyzer := NodeAnalyzer{}\n\tresults, err := nAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"Node1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/operatorgroup.go",
    "content": "package analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype OperatorGroupAnalyzer struct{}\n\nvar ogGVR = schema.GroupVersionResource{\n\tGroup: \"operators.coreos.com\", Version: \"v1\", Resource: \"operatorgroups\",\n}\n\nfunc (OperatorGroupAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"OperatorGroup\"\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in %s analyzer\", kind)\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().\n\t\tResource(ogGVR).Namespace(metav1.NamespaceAll).\n\t\tList(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcountByNS := map[string]int{}\n\tfor _, it := range list.Items {\n\t\tcountByNS[it.GetNamespace()]++\n\t}\n\n\tvar results []common.Result\n\tfor ns, n := range countByNS {\n\t\tif n > 1 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  kind,\n\t\t\t\tName:  ns,\n\t\t\t\tError: []common.Failure{{Text: fmt.Sprintf(\"%d OperatorGroups in namespace; this can break CSV resolution\", n)}},\n\t\t\t})\n\t\t}\n\t}\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/operatorgroup_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n)\n\nfunc TestOperatorGroupAnalyzer(t *testing.T) {\n\tog1 := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1\",\n\t\t\t\"kind\":       \"OperatorGroup\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"og-1\",\n\t\t\t\t\"namespace\": \"ns-a\",\n\t\t\t},\n\t\t},\n\t}\n\tog2 := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1\",\n\t\t\t\"kind\":       \"OperatorGroup\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"og-2\",\n\t\t\t\t\"namespace\": \"ns-a\",\n\t\t\t},\n\t\t},\n\t}\n\tog3 := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1\",\n\t\t\t\"kind\":       \"OperatorGroup\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"og-3\",\n\t\t\t\t\"namespace\": \"ns-b\",\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1\", Resource: \"operatorgroups\"}: \"OperatorGroupList\",\n\t}\n\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, og1, og2, og3)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (OperatorGroupAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\n\tif len(res) != 1 {\n\t\tt.Fatalf(\"expected 1 result for ns-a overlap, got %d\", len(res))\n\t}\n\tif res[0].Kind != \"OperatorGroup\" || res[0].Name != \"ns-a\" {\n\t\tt.Fatalf(\"unexpected result: %#v\", res[0])\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/pdb.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype PdbAnalyzer struct{}\n\nfunc (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"PodDisruptionBudget\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"policy\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tlist, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, pdb := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Before accessing the Conditions, check if they exist or not.\n\t\tif len(pdb.Status.Conditions) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif pdb.Status.Conditions[0].Type == \"DisruptionAllowed\" && pdb.Status.Conditions[0].Status == \"False\" {\n\t\t\tvar doc string\n\t\t\tif pdb.Spec.MaxUnavailable != nil {\n\t\t\t\tdoc = apiDoc.GetApiDocV2(\"spec.maxUnavailable\")\n\t\t\t}\n\t\t\tif pdb.Spec.MinAvailable != nil {\n\t\t\t\tdoc = apiDoc.GetApiDocV2(\"spec.minAvailable\")\n\t\t\t}\n\t\t\tif pdb.Spec.Selector != nil && pdb.Spec.Selector.MatchLabels != nil {\n\t\t\t\tfor k, v := range pdb.Spec.Selector.MatchLabels {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:          fmt.Sprintf(\"%s, expected pdb pod label %s=%s\", pdb.Status.Conditions[0].Reason, k, v),\n\t\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: k,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(k),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: v,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(v),\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\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", pdb.Namespace, pdb.Name)] = common.PreAnalysis{\n\t\t\t\tPodDisruptionBudget: pdb,\n\t\t\t\tFailureDetails:      failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, pdb.Name, pdb.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, err\n}\n"
  },
  {
    "path": "pkg/analyzer/pdb_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tpolicyv1 \"k8s.io/api/policy/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestPodDisruptionBudgetAnalyzer(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB1\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\t// Status conditions are nil.\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: nil,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB2\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\t// Status conditions are empty.\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: []metav1.Condition{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB3\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   \"DisruptionAllowed\",\n\t\t\t\t\t\t\t\tStatus: \"False\",\n\t\t\t\t\t\t\t\tReason: \"test reason\",\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\tSpec: policyv1.PodDisruptionBudgetSpec{\n\t\t\t\t\t\tMaxUnavailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 17,\n\t\t\t\t\t\t\tStrVal: \"17\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tMinAvailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 7,\n\t\t\t\t\t\t\tStrVal: \"7\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t// MatchLabels specified.\n\t\t\t\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\"label1\": \"test1\",\n\t\t\t\t\t\t\t\t\"label2\": \"test2\",\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&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB4\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   \"DisruptionAllowed\",\n\t\t\t\t\t\t\t\tStatus: \"False\",\n\t\t\t\t\t\t\t\tReason: \"test reason\",\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// Match Labels Empty.\n\t\t\t\t\tSpec: policyv1.PodDisruptionBudgetSpec{\n\t\t\t\t\t\tSelector: &metav1.LabelSelector{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"test\",\n\t}\n\n\tpdbAnalyzer := PdbAnalyzer{}\n\tresults, err := pdbAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"test/PDB3\", results[0].Name)\n}\n\nfunc TestPodDisruptionBudgetAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"pdb\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t// Status conditions are nil.\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   \"DisruptionAllowed\",\n\t\t\t\t\t\t\t\tStatus: \"False\",\n\t\t\t\t\t\t\t\tReason: \"test reason\",\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\tSpec: policyv1.PodDisruptionBudgetSpec{\n\t\t\t\t\t\tMaxUnavailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 17,\n\t\t\t\t\t\t\tStrVal: \"17\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tMinAvailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 7,\n\t\t\t\t\t\t\tStrVal: \"7\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t// MatchLabels specified.\n\t\t\t\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\"label1\": \"test1\",\n\t\t\t\t\t\t\t\t\"label2\": \"test2\",\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&policyv1.PodDisruptionBudget{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PDB2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\t// Status conditions are empty.\n\t\t\t\t\tStatus: policyv1.PodDisruptionBudgetStatus{\n\t\t\t\t\t\tConditions: []metav1.Condition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType:   \"DisruptionAllowed\",\n\t\t\t\t\t\t\t\tStatus: \"False\",\n\t\t\t\t\t\t\t\tReason: \"test reason\",\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\tSpec: policyv1.PodDisruptionBudgetSpec{\n\t\t\t\t\t\tMaxUnavailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 17,\n\t\t\t\t\t\t\tStrVal: \"17\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tMinAvailable: &intstr.IntOrString{\n\t\t\t\t\t\t\tType:   0,\n\t\t\t\t\t\t\tIntVal: 7,\n\t\t\t\t\t\t\tStrVal: \"7\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t// MatchLabels specified.\n\t\t\t\t\t\tSelector: &metav1.LabelSelector{\n\t\t\t\t\t\t\tMatchLabels: map[string]string{\n\t\t\t\t\t\t\t\t\"label1\": \"test1\",\n\t\t\t\t\t\t\t\t\"label2\": \"test2\",\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\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=pdb\",\n\t}\n\n\tpdbAnalyzer := PdbAnalyzer{}\n\tresults, err := pdbAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/PDB1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/pod.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype PodAnalyzer struct {\n}\n\nfunc (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Pod\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// search all namespaces for pods that are not running\n\tlist, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, pod := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for pending pods\n\t\tif pod.Status.Phase == \"Pending\" {\n\t\t\t// Check through container status to check for crashes\n\t\t\tfor _, containerStatus := range pod.Status.Conditions {\n\t\t\t\tif containerStatus.Type == v1.PodScheduled && containerStatus.Reason == \"Unschedulable\" {\n\t\t\t\t\tif containerStatus.Message != \"\" {\n\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\tText:      containerStatus.Message,\n\t\t\t\t\t\t\tSensitive: []common.Sensitive{},\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\t// Check for errors in the init containers.\n\t\tfailures = append(failures, analyzeContainerStatusFailures(a, pod.Status.InitContainerStatuses, pod.Name, pod.Namespace, string(pod.Status.Phase))...)\n\n\t\t// Check for errors in containers.\n\t\tfailures = append(failures, analyzeContainerStatusFailures(a, pod.Status.ContainerStatuses, pod.Name, pod.Namespace, string(pod.Status.Phase))...)\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", pod.Namespace, pod.Name)] = common.PreAnalysis{\n\t\t\t\tPod:            pod,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Pod.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\nfunc analyzeContainerStatusFailures(a common.Analyzer, statuses []v1.ContainerStatus, name string, namespace string, statusPhase string) []common.Failure {\n\tvar failures []common.Failure\n\n\t// Check through container status to check for crashes or unready\n\tfor _, containerStatus := range statuses {\n\t\tif containerStatus.State.Waiting != nil {\n\t\t\tif containerStatus.State.Waiting.Reason == \"ContainerCreating\" && statusPhase == \"Pending\" {\n\t\t\t\t// This represents a container that is still being created or blocked due to conditions such as OOMKilled\n\t\t\t\t// parse the event log and append details\n\t\t\t\tevt, err := util.FetchLatestEvent(a.Context, a.Client, namespace, name)\n\t\t\t\tif err != nil || evt == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif isEvtErrorReason(evt.Reason) && evt.Message != \"\" {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      evt.Message,\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if containerStatus.State.Waiting.Reason == \"CrashLoopBackOff\" && containerStatus.LastTerminationState.Terminated != nil {\n\t\t\t\t// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"the last termination reason is %s container=%s pod=%s\", containerStatus.LastTerminationState.Terminated.Reason, containerStatus.Name, name),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t} else if isErrorReason(containerStatus.State.Waiting.Reason) && containerStatus.State.Waiting.Message != \"\" {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      containerStatus.State.Waiting.Message,\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t} else if containerStatus.State.Terminated != nil {\n\t\t\tif containerStatus.State.Terminated.ExitCode != 0 {\n\t\t\t\t// This represents a container that is terminated abnormally\n\t\t\t\t// https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-state-terminated\n\t\t\t\texitCode := containerStatus.State.Terminated.ExitCode\n\t\t\t\treason := containerStatus.State.Terminated.Reason\n\t\t\t\tif reason == \"\" {\n\t\t\t\t\treason = \"Unknown\"\n\t\t\t\t}\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"the termination reason is %s exitCode=%d container=%s pod=%s\", reason, exitCode, containerStatus.Name, name),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t} else {\n\t\t\t// when pod is Running but its ReadinessProbe fails\n\t\t\tif !containerStatus.Ready && statusPhase == \"Running\" {\n\t\t\t\t// parse the event log and append details\n\t\t\t\tevt, err := util.FetchLatestEvent(a.Context, a.Client, namespace, name)\n\t\t\t\tif err != nil || evt == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif evt.Reason == \"Unhealthy\" && evt.Message != \"\" {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      evt.Message,\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn failures\n}\n\nfunc isErrorReason(reason string) bool {\n\tfailureReasons := []string{\n\t\t\"CrashLoopBackOff\", \"ImagePullBackOff\", \"CreateContainerConfigError\", \"PreCreateHookError\", \"CreateContainerError\",\n\t\t\"PreStartHookError\", \"RunContainerError\", \"ImageInspectError\", \"ErrImagePull\", \"ErrImageNeverPull\", \"InvalidImageName\",\n\t}\n\n\tfor _, r := range failureReasons {\n\t\tif r == reason {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc isEvtErrorReason(reason string) bool {\n\tfailureReasons := []string{\n\t\t\"FailedCreatePodSandBox\", \"FailedMount\",\n\t}\n\n\tfor _, r := range failureReasons {\n\t\tif r == reason {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/analyzer/pod_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestPodAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       common.Analyzer\n\t\texpectations []struct {\n\t\t\tname          string\n\t\t\tfailuresCount int\n\t\t}\n\t}{\n\t\t{\n\t\t\tname: \"Pending pods, namespace filtering and readiness probe failure\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\tPhase: v1.PodPending,\n\t\t\t\t\t\t\t\tConditions: []v1.PodCondition{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t// This condition will contribute to failures.\n\t\t\t\t\t\t\t\t\t\tType:    v1.PodScheduled,\n\t\t\t\t\t\t\t\t\t\tReason:  \"Unschedulable\",\n\t\t\t\t\t\t\t\t\t\tMessage: \"0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.\",\n\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\t\t// This condition won't contribute to failures.\n\t\t\t\t\t\t\t\t\t\tType:   v1.PodScheduled,\n\t\t\t\t\t\t\t\t\t\tReason: \"Unexpected failure\",\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\t&v1.Pod{\n\t\t\t\t\t\t\t// This pod won't be selected because of namespace filtering.\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod2\",\n\t\t\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod3\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\t// When pod is Running but its ReadinessProbe fails\n\t\t\t\t\t\t\t\tPhase: v1.PodRunning,\n\t\t\t\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tReady: false,\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\t&v1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tInvolvedObject: v1.ObjectReference{\n\t\t\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\t\t\tName:      \"Pod3\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tReason:  \"Unhealthy\",\n\t\t\t\t\t\t\tMessage: \"readiness probe failed: the detail reason here ...\",\n\t\t\t\t\t\t\tSource:  v1.EventSource{Component: \"eventTest\"},\n\t\t\t\t\t\t\tCount:   1,\n\t\t\t\t\t\t\tType:    v1.EventTypeWarning,\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/Pod1\",\n\t\t\t\t\tfailuresCount: 1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/Pod3\",\n\t\t\t\t\tfailuresCount: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"readiness probe failure without any event\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\t// When pod is Running but its ReadinessProbe fails\n\t\t\t\t\t\t\t\t// It won't contribute to any failures because\n\t\t\t\t\t\t\t\t// there's no event present.\n\t\t\t\t\t\t\t\tPhase: v1.PodRunning,\n\t\t\t\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tReady: false,\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Init container status state waiting\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\tPhase: v1.PodPending,\n\t\t\t\t\t\t\t\tInitContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tReady: true,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tRunning: &v1.ContainerStateRunning{\n\t\t\t\t\t\t\t\t\t\t\t\tStartedAt: metav1.Now(),\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\t{\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// This represents a container that is still being created or blocked due to conditions such as OOMKilled\n\t\t\t\t\t\t\t\t\t\t\t\tReason: \"ContainerCreating\",\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\t&v1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tInvolvedObject: v1.ObjectReference{\n\t\t\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tReason:  \"FailedCreatePodSandBox\",\n\t\t\t\t\t\t\tMessage: \"failed to create the pod sandbox ...\",\n\t\t\t\t\t\t\tType:    v1.EventTypeWarning,\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/Pod1\",\n\t\t\t\t\tfailuresCount: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Container status state waiting but no event reported\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\tPhase: v1.PodPending,\n\t\t\t\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// This represents a container that is still being created or blocked due to conditions such as OOMKilled\n\t\t\t\t\t\t\t\t\t\t\t\tReason: \"ContainerCreating\",\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Container status state waiting\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\tPhase: v1.PodPending,\n\t\t\t\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container1\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// This represents a container that is still being created or blocked due to conditions such as OOMKilled\n\t\t\t\t\t\t\t\t\t\t\t\tReason: \"ContainerCreating\",\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\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container2\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled\n\t\t\t\t\t\t\t\t\t\t\t\tReason: \"CrashLoopBackOff\",\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\tLastTerminationState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tTerminated: &v1.ContainerStateTerminated{\n\t\t\t\t\t\t\t\t\t\t\t\tReason: \"test reason\",\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\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container3\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// This won't contribute to failures.\n\t\t\t\t\t\t\t\t\t\t\t\tReason:  \"RandomReason\",\n\t\t\t\t\t\t\t\t\t\t\t\tMessage: \"This container won't be present in the failures\",\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\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container4\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// Valid error reason.\n\t\t\t\t\t\t\t\t\t\t\t\tReason:  \"PreStartHookError\",\n\t\t\t\t\t\t\t\t\t\t\t\tMessage: \"Container4 encountered PreStartHookError\",\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\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container5\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tWaiting: &v1.ContainerStateWaiting{\n\t\t\t\t\t\t\t\t\t\t\t\t// Valid error reason.\n\t\t\t\t\t\t\t\t\t\t\t\tReason:  \"CrashLoopBackOff\",\n\t\t\t\t\t\t\t\t\t\t\t\tMessage: \"Container4 encountered CrashLoopBackOff\",\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\t&v1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tInvolvedObject: v1.ObjectReference{\n\t\t\t\t\t\t\t\tKind:      \"Pod\",\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// This reason won't contribute to failures.\n\t\t\t\t\t\t\tReason: \"RandomEvent\",\n\t\t\t\t\t\t\tType:   v1.EventTypeWarning,\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/Pod1\",\n\t\t\t\t\tfailuresCount: 3,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Terminated container with non-zero exit code\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Pod{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: v1.PodStatus{\n\t\t\t\t\t\t\t\tPhase: v1.PodFailed,\n\t\t\t\t\t\t\t\tContainerStatuses: []v1.ContainerStatus{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container1\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tTerminated: &v1.ContainerStateTerminated{\n\t\t\t\t\t\t\t\t\t\t\t\tExitCode: 1,\n\t\t\t\t\t\t\t\t\t\t\t\tReason:   \"Error\",\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\t{\n\t\t\t\t\t\t\t\t\t\tName:  \"Container2\",\n\t\t\t\t\t\t\t\t\t\tReady: false,\n\t\t\t\t\t\t\t\t\t\tState: v1.ContainerState{\n\t\t\t\t\t\t\t\t\t\t\tTerminated: &v1.ContainerStateTerminated{\n\t\t\t\t\t\t\t\t\t\t\t\tExitCode: 2,\n\t\t\t\t\t\t\t\t\t\t\t\tReason:   \"\",\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/Pod1\",\n\t\t\t\t\tfailuresCount: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tpodAnalyzer := PodAnalyzer{}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresults, err := podAnalyzer.Analyze(tt.config)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tt.expectations == nil {\n\t\t\t\trequire.Equal(t, 0, len(results))\n\t\t\t} else {\n\t\t\t\tsort.Slice(results, func(i, j int) bool {\n\t\t\t\t\treturn results[i].Name < results[j].Name\n\t\t\t\t})\n\n\t\t\t\trequire.Equal(t, len(tt.expectations), len(results))\n\n\t\t\t\tfor i, result := range results {\n\t\t\t\t\trequire.Equal(t, tt.expectations[i].name, result.Name)\n\t\t\t\t\trequire.Equal(t, tt.expectations[i].failuresCount, len(result.Error))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/pvc.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tappsv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype PvcAnalyzer struct{}\n\nfunc (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"PersistentVolumeClaim\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// search all namespaces for pods that are not running\n\tlist, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, pvc := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for empty rs\n\t\tif pvc.Status.Phase == appsv1.ClaimPending {\n\n\t\t\t// parse the event log and append details\n\t\t\tevt, err := util.FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)\n\t\t\tif err != nil || evt == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif evt.Reason == \"ProvisioningFailed\" && evt.Message != \"\" {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      evt.Message,\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", pvc.Namespace, pvc.Name)] = common.PreAnalysis{\n\t\t\t\tPersistentVolumeClaim: pvc,\n\t\t\t\tFailureDetails:        failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, pvc.Name, pvc.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/pvc_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tappsv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestPersistentVolumeClaimAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       common.Analyzer\n\t\texpectations []string\n\t}{\n\t\t{\n\t\t\tname: \"PV1 and PVC5 report failures\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&appsv1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tLastTimestamp: metav1.Time{\n\t\t\t\t\t\t\t\tTime: time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tReason:  \"ProvisioningFailed\",\n\t\t\t\t\t\t\tMessage: \"PVC Event1 provisioning failed\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\t// This event won't get selected.\n\t\t\t\t\t\t\t\tName:      \"Event2\",\n\t\t\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.Event{\n\t\t\t\t\t\t\t// This is the latest event.\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event3\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tLastTimestamp: metav1.Time{\n\t\t\t\t\t\t\t\tTime: time.Date(2024, 4, 15, 10, 0, 0, 0, time.UTC),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tReason:  \"ProvisioningFailed\",\n\t\t\t\t\t\t\tMessage: \"PVC Event3 provisioning failed\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimPending,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC2\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\t// Won't contribute to failures.\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimBound,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC3\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\t// Won't contribute to failures.\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimLost,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\t// PVCs in namespace other than \"default\" won't be discovered.\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC4\",\n\t\t\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimLost,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\t// PVCs in namespace other than \"default\" won't be discovered.\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC5\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimPending,\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []string{\n\t\t\t\t\"default/PVC1\",\n\t\t\t\t\"default/PVC5\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no event\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimPending,\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"event other than provision failure\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&appsv1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Any reason other than ProvisioningFailed won't result in failure.\n\t\t\t\t\t\t\tReason: \"UnknownReason\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimPending,\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"event without error message\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&appsv1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t// Event without any error message won't result in failure.\n\t\t\t\t\t\t\tReason: \"ProvisioningFailed\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"PVC1\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\t\t\tPhase: appsv1.ClaimPending,\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\tContext:   context.Background(),\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t}\n\n\tpvcAnalyzer := PvcAnalyzer{}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresults, err := pvcAnalyzer.Analyze(tt.config)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tt.expectations == nil {\n\t\t\t\trequire.Equal(t, 0, len(results))\n\t\t\t} else {\n\t\t\t\tsort.Slice(results, func(i, j int) bool {\n\t\t\t\t\treturn results[i].Name < results[j].Name\n\t\t\t\t})\n\n\t\t\t\trequire.Equal(t, len(tt.expectations), len(results))\n\n\t\t\t\tfor i, expectation := range tt.expectations {\n\t\t\t\t\trequire.Equal(t, expectation, results[i].Name)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPvcAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&appsv1.Event{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Event1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tLastTimestamp: metav1.Time{\n\t\t\t\t\t\tTime: time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC),\n\t\t\t\t\t},\n\t\t\t\t\tReason:  \"ProvisioningFailed\",\n\t\t\t\t\tMessage: \"PVC Event1 provisioning failed\",\n\t\t\t\t},\n\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PVC1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"pvc\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\tPhase: appsv1.ClaimPending,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&appsv1.PersistentVolumeClaim{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"PVC2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\tPhase: appsv1.ClaimPending,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=pvc\",\n\t}\n\n\tpvcAnalyzer := PvcAnalyzer{}\n\tresults, err := pvcAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/PVC1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/rs.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype ReplicaSetAnalyzer struct{}\n\nfunc (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"ReplicaSet\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// search all namespaces for pods that are not running\n\tlist, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, rs := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for empty rs\n\t\tif rs.Status.Replicas == 0 {\n\n\t\t\t// Check through container status to check for crashes\n\t\t\tfor _, rsStatus := range rs.Status.Conditions {\n\t\t\t\tif rsStatus.Type == \"ReplicaFailure\" && rsStatus.Reason == \"FailedCreate\" {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      rsStatus.Message,\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", rs.Namespace, rs.Name)] = common.PreAnalysis{\n\t\t\t\tReplicaSet:     rs,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, rs.Name, rs.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/rs_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestReplicaSetAnalyzer(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&appsv1.ReplicaSet{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 0,\n\t\t\t\t\t\tConditions: []appsv1.ReplicaSetCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should contribute to failures.\n\t\t\t\t\t\t\t\tType:    appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason:  \"FailedCreate\",\n\t\t\t\t\t\t\t\tMessage: \"failed to create test replica set 1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&appsv1.ReplicaSet{\n\t\t\t\t\t// This replicaset won't be discovered as it is not in the\n\t\t\t\t\t// default namespace.\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet2\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&appsv1.ReplicaSet{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet3\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 0,\n\t\t\t\t\t\tConditions: []appsv1.ReplicaSetCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tType: appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\t// Should not be included in the failures.\n\t\t\t\t\t\t\t\tReason: \"RandomError\",\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&appsv1.ReplicaSet{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet4\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 0,\n\t\t\t\t\t\tConditions: []appsv1.ReplicaSetCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should contribute to failures.\n\t\t\t\t\t\t\t\tType:    appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason:  \"FailedCreate\",\n\t\t\t\t\t\t\t\tMessage: \"failed to create test replica set 4 condition 1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should not contribute to failures.\n\t\t\t\t\t\t\t\tType:   appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason: \"Unknown\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should not contribute to failures.\n\t\t\t\t\t\t\t\tType:    appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason:  \"FailedCreate\",\n\t\t\t\t\t\t\t\tMessage: \"failed to create test replica set 4 condition 3\",\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&appsv1.ReplicaSet{\n\t\t\t\t\t// Replicaset without any failures.\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet5\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 3,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\trsAnalyzer := ReplicaSetAnalyzer{}\n\tresults, err := rsAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\n\tsort.Slice(results, func(i, j int) bool {\n\t\treturn results[i].Name < results[j].Name\n\t})\n\n\texpectations := []struct {\n\t\tname          string\n\t\tfailuresCount int\n\t}{\n\t\t{\n\t\t\tname:          \"default/ReplicaSet1\",\n\t\t\tfailuresCount: 1,\n\t\t},\n\t\t{\n\t\t\tname:          \"default/ReplicaSet4\",\n\t\t\tfailuresCount: 2,\n\t\t},\n\t}\n\n\trequire.Equal(t, len(expectations), len(results))\n\n\tfor i, result := range results {\n\t\trequire.Equal(t, expectations[i].name, result.Name)\n\t\trequire.Equal(t, expectations[i].failuresCount, len(result.Error))\n\t}\n}\n\nfunc TestReplicaSetAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&appsv1.ReplicaSet{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"app\": \"replicaset\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 0,\n\t\t\t\t\t\tConditions: []appsv1.ReplicaSetCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should contribute to failures.\n\t\t\t\t\t\t\t\tType:    appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason:  \"FailedCreate\",\n\t\t\t\t\t\t\t\tMessage: \"failed to create test replica set 1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&appsv1.ReplicaSet{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"ReplicaSet2\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: appsv1.ReplicaSetStatus{\n\t\t\t\t\t\tReplicas: 0,\n\t\t\t\t\t\tConditions: []appsv1.ReplicaSetCondition{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Should contribute to failures.\n\t\t\t\t\t\t\t\tType:    appsv1.ReplicaSetReplicaFailure,\n\t\t\t\t\t\t\t\tReason:  \"FailedCreate\",\n\t\t\t\t\t\t\t\tMessage: \"failed to create test replica set 1\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t),\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=replicaset\",\n\t}\n\n\trsAnalyzer := ReplicaSetAnalyzer{}\n\tresults, err := rsAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/ReplicaSet1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/security.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype SecurityAnalyzer struct{}\n\nfunc (SecurityAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"Security\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tvar results []common.Result\n\n\t// Analyze ServiceAccounts\n\tsaResults, err := analyzeServiceAccounts(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, saResults...)\n\n\t// Analyze RoleBindings\n\trbResults, err := analyzeRoleBindings(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, rbResults...)\n\n\t// Analyze Pod Security Contexts\n\tpodResults, err := analyzePodSecurityContexts(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, podResults...)\n\n\treturn results, nil\n}\n\nfunc analyzeServiceAccounts(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\tsas, err := a.Client.GetClient().CoreV1().ServiceAccounts(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, sa := range sas.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for default service account usage\n\t\tif sa.Name == \"default\" {\n\t\t\tpods, err := a.Client.GetClient().CoreV1().Pods(sa.Namespace).List(a.Context, metav1.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdefaultSAUsers := []string{}\n\t\t\tfor _, pod := range pods.Items {\n\t\t\t\tif pod.Spec.ServiceAccountName == \"default\" {\n\t\t\t\t\tdefaultSAUsers = append(defaultSAUsers, pod.Name)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(defaultSAUsers) > 0 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"Default service account is being used by pods: %v\", defaultSAUsers),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Security/ServiceAccount\",\n\t\t\t\tName:  fmt.Sprintf(\"%s/%s\", sa.Namespace, sa.Name),\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Security/ServiceAccount\", sa.Name, sa.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc analyzeRoleBindings(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\trbs, err := a.Client.GetClient().RbacV1().RoleBindings(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, rb := range rbs.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for wildcards in role references\n\t\trole, err := a.Client.GetClient().RbacV1().Roles(rb.Namespace).Get(a.Context, rb.RoleRef.Name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, rule := range role.Rules {\n\t\t\tif containsWildcard(rule.Verbs) || containsWildcard(rule.Resources) {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"RoleBinding %s references Role %s which contains wildcard permissions - this is not recommended for security best practices\", rb.Name, role.Name),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Security/RoleBinding\",\n\t\t\t\tName:  fmt.Sprintf(\"%s/%s\", rb.Namespace, rb.Name),\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Security/RoleBinding\", rb.Name, rb.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc analyzePodSecurityContexts(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\tpods, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, pod := range pods.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for privileged containers first (most critical)\n\t\thasPrivilegedContainer := false\n\t\tfor _, container := range pod.Spec.Containers {\n\t\t\tif container.SecurityContext != nil && container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"Container %s in pod %s is running as privileged which poses security risks\", container.Name, pod.Name),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t\thasPrivilegedContainer = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// Only check for missing security context if no privileged containers found\n\t\tif !hasPrivilegedContainer && pod.Spec.SecurityContext == nil {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"Pod %s does not have a security context defined which may pose security risks\", pod.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Security/Pod\",\n\t\t\t\tName:  fmt.Sprintf(\"%s/%s\", pod.Namespace, pod.Name),\n\t\t\t\tError: failures[:1],\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Security/Pod\", pod.Name, pod.Namespace).Set(1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc containsWildcard(slice []string) bool {\n\tfor _, item := range slice {\n\t\tif item == \"*\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/analyzer/security_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\trbacv1 \"k8s.io/api/rbac/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestSecurityAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tnamespace       string\n\t\tserviceAccounts []v1.ServiceAccount\n\t\tpods            []v1.Pod\n\t\troles           []rbacv1.Role\n\t\troleBindings    []rbacv1.RoleBinding\n\t\texpectedErrors  int\n\t\texpectedKinds   []string\n\t}{\n\t\t{\n\t\t\tname:      \"default service account usage\",\n\t\t\tnamespace: \"default\",\n\t\t\tserviceAccounts: []v1.ServiceAccount{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"default\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpods: []v1.Pod{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-pod\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tServiceAccountName: \"default\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 2,\n\t\t\texpectedKinds:  []string{\"Security/ServiceAccount\", \"Security/Pod\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"privileged container\",\n\t\t\tnamespace: \"default\",\n\t\t\tpods: []v1.Pod{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"privileged-pod\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PodSpec{\n\t\t\t\t\t\tContainers: []v1.Container{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tName: \"privileged-container\",\n\t\t\t\t\t\t\t\tSecurityContext: &v1.SecurityContext{\n\t\t\t\t\t\t\t\t\tPrivileged: boolPtr(true),\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\texpectedErrors: 1,\n\t\t\texpectedKinds:  []string{\"Security/Pod\"},\n\t\t},\n\t\t{\n\t\t\tname:      \"wildcard permissions in role\",\n\t\t\tnamespace: \"default\",\n\t\t\troles: []rbacv1.Role{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"wildcard-role\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tRules: []rbacv1.PolicyRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVerbs:     []string{\"*\"},\n\t\t\t\t\t\t\tResources: []string{\"pods\"},\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\troleBindings: []rbacv1.RoleBinding{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-binding\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tRoleRef: rbacv1.RoleRef{\n\t\t\t\t\t\tKind: \"Role\",\n\t\t\t\t\t\tName: \"wildcard-role\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t\texpectedKinds:  []string{\"Security/RoleBinding\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclient := fake.NewSimpleClientset()\n\n\t\t\t// Create test resources\n\t\t\tfor _, sa := range tt.serviceAccounts {\n\t\t\t\t_, err := client.CoreV1().ServiceAccounts(tt.namespace).Create(context.TODO(), &sa, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor _, pod := range tt.pods {\n\t\t\t\t_, err := client.CoreV1().Pods(tt.namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor _, role := range tt.roles {\n\t\t\t\t_, err := client.RbacV1().Roles(tt.namespace).Create(context.TODO(), &role, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tfor _, rb := range tt.roleBindings {\n\t\t\t\t_, err := client.RbacV1().RoleBindings(tt.namespace).Create(context.TODO(), &rb, metav1.CreateOptions{})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\n\t\t\tanalyzer := SecurityAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(common.Analyzer{\n\t\t\t\tClient:    &kubernetes.Client{Client: client},\n\t\t\t\tContext:   context.TODO(),\n\t\t\t\tNamespace: tt.namespace,\n\t\t\t})\n\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Debug: Print all results\n\t\t\tt.Logf(\"Got %d results:\", len(results))\n\t\t\tfor _, result := range results {\n\t\t\t\tt.Logf(\"  Kind: %s, Name: %s\", result.Kind, result.Name)\n\t\t\t\tfor _, failure := range result.Error {\n\t\t\t\t\tt.Logf(\"    Failure: %s\", failure.Text)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Count results by kind\n\t\t\tresultsByKind := make(map[string]int)\n\t\t\tfor _, result := range results {\n\t\t\t\tresultsByKind[result.Kind]++\n\t\t\t}\n\n\t\t\t// Check that we have the expected number of results for each kind\n\t\t\tfor _, expectedKind := range tt.expectedKinds {\n\t\t\t\tassert.Equal(t, 1, resultsByKind[expectedKind], \"Expected 1 result of kind %s\", expectedKind)\n\t\t\t}\n\n\t\t\t// Check total number of results matches expected kinds\n\t\t\tassert.Equal(t, len(tt.expectedKinds), len(results), \"Expected %d total results\", len(tt.expectedKinds))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/service.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/fatih/color\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/tools/leaderelection/resourcelock\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype ServiceAnalyzer struct{}\n\nfunc (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"Service\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\t// search all namespaces for pods that are not running\n\tlist, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, ep := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for empty service\n\t\tif len(ep.Subsets) == 0 {\n\t\t\tif _, ok := ep.Annotations[resourcelock.LeaderElectionRecordAnnotationKey]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsvc, err := a.Client.GetClient().CoreV1().Services(ep.Namespace).Get(a.Context, ep.Name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tcolor.Yellow(\"Service %s/%s does not exist\", ep.Namespace, ep.Name)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor k, v := range svc.Spec.Selector {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.selector\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Service has no endpoints, expected label %s=%s\", k, v),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: k,\n\t\t\t\t\t\t\tMasked:   util.MaskString(k),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: v,\n\t\t\t\t\t\t\tMasked:   util.MaskString(v),\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} else {\n\t\t\tcount := 0\n\t\t\tpods := []string{}\n\n\t\t\t// Check through container status to check for crashes\n\t\t\tfor _, epSubset := range ep.Subsets {\n\t\t\t\tapiDoc.Kind = \"Endpoints\"\n\n\t\t\t\tif len(epSubset.NotReadyAddresses) > 0 {\n\t\t\t\t\tfor _, addresses := range epSubset.NotReadyAddresses {\n\t\t\t\t\t\tcount++\n\t\t\t\t\t\tpods = append(pods, addresses.TargetRef.Kind+\"/\"+addresses.TargetRef.Name)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif count > 0 {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"subsets.notReadyAddresses\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Service has not ready endpoints, pods: %s, expected %d\", pods, count),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive:     []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\t// fetch event\n\t\tevents, err := a.Client.GetClient().CoreV1().Events(a.Namespace).List(a.Context,\n\t\t\tmetav1.ListOptions{\n\t\t\t\tFieldSelector: \"involvedObject.name=\" + ep.Name,\n\t\t\t})\n\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, event := range events.Items {\n\t\t\tif event.Type != \"Normal\" {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText: fmt.Sprintf(\"Service %s/%s has event %s\", ep.Namespace, ep.Name, event.Message),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", ep.Namespace, ep.Name)] = common.PreAnalysis{\n\t\t\t\tEndpoint:       ep,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, ep.Name, ep.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.Endpoint.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/service_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestServiceAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tconfig       common.Analyzer\n\t\texpectations []struct {\n\t\t\tname          string\n\t\t\tfailuresCount int\n\t\t}\n\t}{\n\t\t{\n\t\t\tname: \"Service with no endpoints\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Endpoints{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSubsets: []v1.EndpointSubset{}, // Empty subsets\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&v1.Service{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"test\",\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\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/test-service\",\n\t\t\t\t\tfailuresCount: 1, // One failure for no endpoints\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Service with not ready endpoints\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Endpoints{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSubsets: []v1.EndpointSubset{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tNotReadyAddresses: []v1.EndpointAddress{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tTargetRef: &v1.ObjectReference{\n\t\t\t\t\t\t\t\t\t\t\t\tKind: \"Pod\",\n\t\t\t\t\t\t\t\t\t\t\t\tName: \"test-pod\",\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\t&v1.Service{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"test\",\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\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/test-service\",\n\t\t\t\t\tfailuresCount: 1, // One failure for not ready endpoints\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Service with warning events\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Endpoints{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSubsets: []v1.EndpointSubset{}, // Empty subsets\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&v1.Service{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"test\",\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\t&v1.Event{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-event\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tInvolvedObject: v1.ObjectReference{\n\t\t\t\t\t\t\t\tKind:      \"Service\",\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tType:    \"Warning\",\n\t\t\t\t\t\t\tReason:  \"TestReason\",\n\t\t\t\t\t\t\tMessage: \"Test warning message\",\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t{\n\t\t\t\t\tname:          \"default/test-service\",\n\t\t\t\t\tfailuresCount: 2, // One failure for no endpoints, one for warning event\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Service with leader election annotation\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Endpoints{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\t\t\"control-plane.alpha.kubernetes.io/leader\": \"test-leader\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSubsets: []v1.EndpointSubset{}, // Empty subsets\n\t\t\t\t\t\t},\n\t\t\t\t\t\t&v1.Service{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\t\t\"app\": \"test\",\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\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t// No expectations for leader election endpoints\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Service with non-existent service\",\n\t\t\tconfig: common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t\t\t&v1.Endpoints{\n\t\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\t\tName:      \"test-service\",\n\t\t\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tSubsets: []v1.EndpointSubset{}, // Empty subsets\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\texpectations: []struct {\n\t\t\t\tname          string\n\t\t\t\tfailuresCount int\n\t\t\t}{\n\t\t\t\t// No expectations for non-existent service\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tanalyzer := ServiceAnalyzer{}\n\t\t\tresults, err := analyzer.Analyze(tt.config)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Len(t, results, len(tt.expectations))\n\n\t\t\t// Sort results by name for consistent comparison\n\t\t\tsort.Slice(results, func(i, j int) bool {\n\t\t\t\treturn results[i].Name < results[j].Name\n\t\t\t})\n\n\t\t\tfor i, expectation := range tt.expectations {\n\t\t\t\trequire.Equal(t, expectation.name, results[i].Name)\n\t\t\t\trequire.Len(t, results[i].Error, expectation.failuresCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServiceAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tclientSet :=\n\t\tfake.NewSimpleClientset(\n\t\t\t&v1.Endpoints{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"Endpoint1\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"app\":     \"service\",\n\t\t\t\t\t\t\"part-of\": \"test\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t// Endpoint with non-zero subsets.\n\t\t\t\tSubsets: []v1.EndpointSubset{\n\t\t\t\t\t{\n\t\t\t\t\t\t// These not ready end points will contribute to failures.\n\t\t\t\t\t\tNotReadyAddresses: []v1.EndpointAddress{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tTargetRef: &v1.ObjectReference{\n\t\t\t\t\t\t\t\t\tKind: \"test-reference\",\n\t\t\t\t\t\t\t\t\tName: \"reference1\",\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\tTargetRef: &v1.ObjectReference{\n\t\t\t\t\t\t\t\t\tKind: \"test-reference\",\n\t\t\t\t\t\t\t\t\tName: \"reference2\",\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\t// These not ready end points will contribute to failures.\n\t\t\t\t\t\tNotReadyAddresses: []v1.EndpointAddress{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tTargetRef: &v1.ObjectReference{\n\t\t\t\t\t\t\t\t\tKind: \"test-reference\",\n\t\t\t\t\t\t\t\t\tName: \"reference3\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"Service1\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\"app\": \"service\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\"app1\": \"test-app1\",\n\t\t\t\t\t\t\"app2\": \"test-app2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t&v1.Service{\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:      \"Service2\",\n\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t},\n\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\"app1\": \"test-app1\",\n\t\t\t\t\t\t\"app2\": \"test-app2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=service\",\n\t}\n\n\tsAnalyzer := ServiceAnalyzer{}\n\tresults, err := sAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/Endpoint1\", results[0].Name)\n\n\tconfig = common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=service,part-of=test\",\n\t}\n\n\tsAnalyzer = ServiceAnalyzer{}\n\tresults, err = sAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(results))\n\trequire.Equal(t, \"default/Endpoint1\", results[0].Name)\n}\n"
  },
  {
    "path": "pkg/analyzer/statefulset.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\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)\n\ntype StatefulSetAnalyzer struct{}\n\nfunc (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"StatefulSet\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"apps\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tlist, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, sts := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\t// get serviceName\n\t\tserviceName := sts.Spec.ServiceName\n\t\t_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.serviceName\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\"StatefulSet uses the service %s/%s which does not exist.\",\n\t\t\t\t\tsts.Namespace,\n\t\t\t\t\tserviceName,\n\t\t\t\t),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: sts.Namespace,\n\t\t\t\t\t\tMasked:   util.MaskString(sts.Namespace),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: serviceName,\n\t\t\t\t\t\tMasked:   util.MaskString(serviceName),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\tif len(sts.Spec.VolumeClaimTemplates) > 0 {\n\t\t\tfor _, volumeClaimTemplate := range sts.Spec.VolumeClaimTemplates {\n\t\t\t\tif volumeClaimTemplate.Spec.StorageClassName != nil {\n\t\t\t\t\t_, err := a.Client.GetClient().StorageV1().StorageClasses().Get(a.Context, *volumeClaimTemplate.Spec.StorageClassName, metav1.GetOptions{})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\tText: fmt.Sprintf(\"StatefulSet uses the storage class %s which does not exist.\", *volumeClaimTemplate.Spec.StorageClassName),\n\t\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tUnmasked: *volumeClaimTemplate.Spec.StorageClassName,\n\t\t\t\t\t\t\t\t\tMasked:   util.MaskString(*volumeClaimTemplate.Spec.StorageClassName),\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\tif sts.Spec.Replicas != nil && *(sts.Spec.Replicas) != sts.Status.AvailableReplicas {\n\t\t\tfor i := int32(0); i < *(sts.Spec.Replicas); i++ {\n\t\t\t\tpodName := sts.Name + \"-\" + fmt.Sprint(i)\n\t\t\t\tpod, err := a.Client.GetClient().CoreV1().Pods(sts.Namespace).Get(a.Context, podName, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.IsNotFound(err) && i == 0 {\n\t\t\t\t\t\tevt, err := util.FetchLatestEvent(a.Context, a.Client, sts.Namespace, sts.Name)\n\t\t\t\t\t\tif err != nil || evt == nil || evt.Type == \"Normal\" {\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\t\tText:      evt.Message,\n\t\t\t\t\t\t\tSensitive: []common.Sensitive{},\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\tif pod.Status.Phase != \"Running\" {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText: fmt.Sprintf(\"Statefulset pod %s in the namespace %s is not in running state.\", pod.Name, pod.Namespace),\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: sts.Namespace,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Name),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: serviceName,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Namespace),\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\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", sts.Namespace, sts.Name)] = common.PreAnalysis{\n\t\t\t\tStatefulSet:    sts,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, sts.Name, sts.Namespace).Set(float64(len(failures)))\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/statefulset_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/magiconair/properties/assert\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestStatefulSetAnalyzer(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t})\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestStatefulSetAnalyzerWithoutService(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tServiceName: \"example-svc\",\n\t\t\t},\n\t\t})\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvar errorFound bool\n\twant := \"StatefulSet uses the service default/example-svc which does not exist.\"\n\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !errorFound {\n\t\tt.Errorf(\"Error expected: '%v', not found in StatefulSet's analysis results\", want)\n\t}\n}\n\nfunc TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {\n\tstorageClassName := \"example-sc\"\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tServiceName: \"example-svc\",\n\t\t\t\tVolumeClaimTemplates: []corev1.PersistentVolumeClaim{\n\t\t\t\t\t{\n\t\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\t\tKind:       \"PersistentVolumeClaim\",\n\t\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\t\tName: \"pvc-example\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\tSpec: corev1.PersistentVolumeClaimSpec{\n\t\t\t\t\t\t\tStorageClassName: &storageClassName,\n\t\t\t\t\t\t\tAccessModes: []corev1.PersistentVolumeAccessMode{\n\t\t\t\t\t\t\t\t\"ReadWriteOnce\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tResources: corev1.VolumeResourceRequirements{\n\t\t\t\t\t\t\t\tRequests: corev1.ResourceList{\n\t\t\t\t\t\t\t\t\tcorev1.ResourceStorage: resource.MustParse(\"1Gi\"),\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\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tvar errorFound bool\n\twant := \"StatefulSet uses the storage class example-sc which does not exist.\"\n\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !errorFound {\n\t\tt.Errorf(\"Error expected: '%v', not found in StatefulSet's analysis results\", want)\n\t}\n\n}\n\nfunc TestStatefulSetAnalyzerNamespaceFiltering(t *testing.T) {\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"other-namespace\",\n\t\t\t},\n\t\t})\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestStatefulSetAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example1\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\":     \"statefulset\",\n\t\t\t\t\t\"part-of\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t},\n\t)\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=statefulset\",\n\t}\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\tresults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, 1, len(results))\n\tassert.Equal(t, \"default/example1\", results[0].Name)\n\n\tconfig = common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=statefulset,part-of=test\",\n\t}\n\tstatefulSetAnalyzer = StatefulSetAnalyzer{}\n\tresults, err = statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, 1, len(results))\n\tassert.Equal(t, \"default/example1\", results[0].Name)\n}\n\nfunc TestStatefulSetAnalyzerReplica(t *testing.T) {\n\treplicas := int32(3)\n\tpods := []*corev1.Pod{\n\t\t{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example-0\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: corev1.PodRunning,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example-1\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: corev1.PodRunning,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example-2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: corev1.PodRunning,\n\t\t\t},\n\t\t},\n\t}\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tReplicas: &replicas,\n\t\t\t},\n\t\t\tStatus: appsv1.StatefulSetStatus{\n\t\t\t\tAvailableReplicas: 3,\n\t\t\t},\n\t\t},\n\t\tpods[0], pods[1], pods[2],\n\t)\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestStatefulSetAnalyzerUnavailableReplicas(t *testing.T) {\n\treplicas := int32(3)\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tReplicas: &replicas,\n\t\t\t},\n\t\t\tStatus: appsv1.StatefulSetStatus{\n\t\t\t\tAvailableReplicas: 0,\n\t\t\t},\n\t\t})\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tassert.Equal(t, len(analysisResults), 1)\n}\n\nfunc TestStatefulSetAnalyzerUnavailableReplicaWithPodInitialized(t *testing.T) {\n\treplicas := int32(3)\n\tpods := []*corev1.Pod{\n\t\t{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example-0\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: corev1.PodRunning,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example-1\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tStatus: corev1.PodStatus{\n\t\t\t\tPhase: corev1.PodPending,\n\t\t\t},\n\t\t},\n\t}\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"example\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tSpec: appsv1.StatefulSetSpec{\n\t\t\t\tReplicas: &replicas,\n\t\t\t},\n\t\t\tStatus: appsv1.StatefulSetStatus{\n\t\t\t\tAvailableReplicas: 1,\n\t\t\t},\n\t\t},\n\t\tpods[0], pods[1],\n\t)\n\tstatefulSetAnalyzer := StatefulSetAnalyzer{}\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientset,\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\tanalysisResults, err := statefulSetAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tvar errorFound bool\n\twant := \"Statefulset pod example-1 in the namespace default is not in running state.\"\n\n\tfor _, analysis := range analysisResults {\n\t\tfor _, got := range analysis.Error {\n\t\t\tif want == got.Text {\n\t\t\t\terrorFound = true\n\t\t\t}\n\t\t}\n\t\tif errorFound {\n\t\t\tbreak\n\t\t}\n\t}\n\tif !errorFound {\n\t\tt.Errorf(\"Error expected: '%v', not found in StatefulSet's analysis results\", want)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/storage.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\ntype StorageAnalyzer struct{}\n\nfunc (StorageAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"Storage\"\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tvar results []common.Result\n\n\t// Analyze StorageClasses\n\tscResults, err := analyzeStorageClasses(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, scResults...)\n\n\t// Analyze PersistentVolumes\n\tpvResults, err := analyzePersistentVolumes(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, pvResults...)\n\n\t// Analyze PVCs with enhanced checks\n\tpvcResults, err := analyzePersistentVolumeClaims(a)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresults = append(results, pvcResults...)\n\n\treturn results, nil\n}\n\nfunc analyzeStorageClasses(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\tscs, err := a.Client.GetClient().StorageV1().StorageClasses().List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, sc := range scs.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for deprecated storage classes\n\t\tif sc.Provisioner == \"kubernetes.io/no-provisioner\" {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"StorageClass %s uses deprecated provisioner 'kubernetes.io/no-provisioner'\", sc.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\t// Check for default storage class\n\t\tif sc.Annotations[\"storageclass.kubernetes.io/is-default-class\"] == \"true\" {\n\t\t\t// Check if there are multiple default storage classes\n\t\t\tdefaultCount := 0\n\t\t\tfor _, otherSc := range scs.Items {\n\t\t\t\tif otherSc.Annotations[\"storageclass.kubernetes.io/is-default-class\"] == \"true\" {\n\t\t\t\t\tdefaultCount++\n\t\t\t\t}\n\t\t\t}\n\t\t\tif defaultCount > 1 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"Multiple default StorageClasses found (%d), which can cause confusion\", defaultCount),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Storage/StorageClass\",\n\t\t\t\tName:  sc.Name,\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Storage/StorageClass\", sc.Name, \"\").Set(float64(len(failures)))\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc analyzePersistentVolumes(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\tpvs, err := a.Client.GetClient().CoreV1().PersistentVolumes().List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, pv := range pvs.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for released PVs\n\t\tif pv.Status.Phase == v1.VolumeReleased {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"PersistentVolume %s is in Released state and should be cleaned up\", pv.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\t// Check for failed PVs\n\t\tif pv.Status.Phase == v1.VolumeFailed {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"PersistentVolume %s is in Failed state\", pv.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\t// Check for small PVs (less than 1Gi)\n\t\tif capacity, ok := pv.Spec.Capacity[v1.ResourceStorage]; ok {\n\t\t\tif capacity.Cmp(resource.MustParse(\"1Gi\")) < 0 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"PersistentVolume %s has small capacity (%s)\", pv.Name, capacity.String()),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Storage/PersistentVolume\",\n\t\t\t\tName:  pv.Name,\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Storage/PersistentVolume\", pv.Name, \"\").Set(float64(len(failures)))\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc analyzePersistentVolumeClaims(a common.Analyzer) ([]common.Result, error) {\n\tvar results []common.Result\n\n\tpvcs, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{\n\t\tLabelSelector: a.LabelSelector,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, pvc := range pvcs.Items {\n\t\tvar failures []common.Failure\n\n\t\t// Check for PVC state issues first (most critical)\n\t\tswitch pvc.Status.Phase {\n\t\tcase v1.ClaimPending:\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"PersistentVolumeClaim %s is in Pending state\", pvc.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\tcase v1.ClaimLost:\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"PersistentVolumeClaim %s is in Lost state\", pvc.Name),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\tdefault:\n\t\t\t// Only check other issues if PVC is not in a critical state\n\t\t\tif capacity, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]; ok {\n\t\t\t\tif capacity.Cmp(resource.MustParse(\"1Gi\")) < 0 {\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText:      fmt.Sprintf(\"PersistentVolumeClaim %s has small capacity (%s)\", pvc.Name, capacity.String()),\n\t\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check for missing storage class\n\t\t\tif pvc.Spec.StorageClassName == nil && pvc.Spec.VolumeName == \"\" {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"PersistentVolumeClaim %s has no StorageClass specified\", pvc.Name),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Only report the first failure found\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  \"Storage/PersistentVolumeClaim\",\n\t\t\t\tName:  fmt.Sprintf(\"%s/%s\", pvc.Namespace, pvc.Name),\n\t\t\t\tError: failures[:1],\n\t\t\t})\n\t\t\tAnalyzerErrorsMetric.WithLabelValues(\"Storage/PersistentVolumeClaim\", pvc.Name, pvc.Namespace).Set(1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/storage_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\tv1 \"k8s.io/api/core/v1\"\n\tstoragev1 \"k8s.io/api/storage/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestStorageAnalyzer(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tnamespace      string\n\t\tstorageClasses []storagev1.StorageClass\n\t\tpvs            []v1.PersistentVolume\n\t\tpvcs           []v1.PersistentVolumeClaim\n\t\texpectedErrors int\n\t}{\n\t\t{\n\t\t\tname:      \"Deprecated StorageClass\",\n\t\t\tnamespace: \"default\",\n\t\t\tstorageClasses: []storagev1.StorageClass{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"deprecated-sc\",\n\t\t\t\t\t},\n\t\t\t\t\tProvisioner: \"kubernetes.io/no-provisioner\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Multiple Default StorageClasses\",\n\t\t\tnamespace: \"default\",\n\t\t\tstorageClasses: []storagev1.StorageClass{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"default-sc1\",\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\"storageclass.kubernetes.io/is-default-class\": \"true\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tProvisioner: \"kubernetes.io/gce-pd\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"default-sc2\",\n\t\t\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\t\t\t\"storageclass.kubernetes.io/is-default-class\": \"true\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tProvisioner: \"kubernetes.io/aws-ebs\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 2,\n\t\t},\n\t\t{\n\t\t\tname:      \"Released PV\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvs: []v1.PersistentVolume{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"released-pv\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.PersistentVolumeStatus{\n\t\t\t\t\t\tPhase: v1.VolumeReleased,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Failed PV\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvs: []v1.PersistentVolume{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"failed-pv\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.PersistentVolumeStatus{\n\t\t\t\t\t\tPhase: v1.VolumeFailed,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Small PV\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvs: []v1.PersistentVolume{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName: \"small-pv\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PersistentVolumeSpec{\n\t\t\t\t\t\tCapacity: v1.ResourceList{\n\t\t\t\t\t\t\tv1.ResourceStorage: resource.MustParse(\"500Mi\"),\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\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Pending PVC\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvcs: []v1.PersistentVolumeClaim{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"pending-pvc\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\tPhase: v1.ClaimPending,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Lost PVC\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvcs: []v1.PersistentVolumeClaim{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"lost-pvc\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tStatus: v1.PersistentVolumeClaimStatus{\n\t\t\t\t\t\tPhase: v1.ClaimLost,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Small PVC\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvcs: []v1.PersistentVolumeClaim{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"small-pvc\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PersistentVolumeClaimSpec{\n\t\t\t\t\t\tResources: v1.VolumeResourceRequirements{\n\t\t\t\t\t\t\tRequests: v1.ResourceList{\n\t\t\t\t\t\t\t\tv1.ResourceStorage: resource.MustParse(\"500Mi\"),\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\texpectedErrors: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"PVC without StorageClass\",\n\t\t\tnamespace: \"default\",\n\t\t\tpvcs: []v1.PersistentVolumeClaim{\n\t\t\t\t{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"no-sc-pvc\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.PersistentVolumeClaimSpec{\n\t\t\t\t\t\tResources: v1.VolumeResourceRequirements{\n\t\t\t\t\t\t\tRequests: v1.ResourceList{\n\t\t\t\t\t\t\t\tv1.ResourceStorage: resource.MustParse(\"1Gi\"),\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\texpectedErrors: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Create fake client\n\t\t\tclient := fake.NewSimpleClientset()\n\n\t\t\t// Create test resources\n\t\t\tfor _, sc := range tt.storageClasses {\n\t\t\t\t_, err := client.StorageV1().StorageClasses().Create(context.TODO(), &sc, metav1.CreateOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to create StorageClass: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, pv := range tt.pvs {\n\t\t\t\t_, err := client.CoreV1().PersistentVolumes().Create(context.TODO(), &pv, metav1.CreateOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to create PV: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, pvc := range tt.pvcs {\n\t\t\t\t_, err := client.CoreV1().PersistentVolumeClaims(tt.namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to create PVC: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create analyzer\n\t\t\tanalyzer := StorageAnalyzer{}\n\n\t\t\t// Create analyzer config\n\t\t\tconfig := common.Analyzer{\n\t\t\t\tClient: &kubernetes.Client{\n\t\t\t\t\tClient: client,\n\t\t\t\t},\n\t\t\t\tContext:   context.TODO(),\n\t\t\t\tNamespace: tt.namespace,\n\t\t\t}\n\n\t\t\t// Run analysis\n\t\t\tresults, err := analyzer.Analyze(config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to run analysis: %v\", err)\n\t\t\t}\n\n\t\t\t// Count total errors\n\t\t\ttotalErrors := 0\n\t\t\tfor _, result := range results {\n\t\t\t\ttotalErrors += len(result.Error)\n\t\t\t}\n\n\t\t\t// Check error count\n\t\t\tif totalErrors != tt.expectedErrors {\n\t\t\t\tt.Errorf(\"Expected %d errors, got %d\", tt.expectedErrors, totalErrors)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/subscription.go",
    "content": "package analyzer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\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\ntype SubscriptionAnalyzer struct{}\n\nvar subGVR = schema.GroupVersionResource{\n\tGroup: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"subscriptions\",\n}\n\nfunc (SubscriptionAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkind := \"Subscription\"\n\tif a.Client.GetDynamicClient() == nil {\n\t\treturn nil, fmt.Errorf(\"dynamic client is nil in %s analyzer\", kind)\n\t}\n\n\tlist, err := a.Client.GetDynamicClient().\n\t\tResource(subGVR).Namespace(metav1.NamespaceAll).\n\t\tList(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar results []common.Result\n\tfor _, item := range list.Items {\n\t\tns, name := item.GetNamespace(), item.GetName()\n\t\tstate, _, _ := unstructured.NestedString(item.Object, \"status\", \"state\")\n\t\tconds, _, _ := unstructured.NestedSlice(item.Object, \"status\", \"conditions\")\n\n\t\tvar failures []common.Failure\n\t\tif state == \"\" || state == \"UpgradePending\" || state == \"UpgradeAvailable\" {\n\t\t\tmsg := \"subscription not at latest\"\n\t\t\tif c := pickWorstCondition(conds); c != \"\" {\n\t\t\t\tmsg += \"; \" + c\n\t\t\t}\n\t\t\tfailures = append(failures, common.Failure{Text: fmt.Sprintf(\"state=%q: %s\", state, msg)})\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tresults = append(results, common.Result{\n\t\t\t\tKind:  kind,\n\t\t\t\tName:  ns + \"/\" + name,\n\t\t\t\tError: failures,\n\t\t\t})\n\t\t}\n\t}\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/subscription_test.go",
    "content": "package analyzer\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\tdynamicfake \"k8s.io/client-go/dynamic/fake\"\n)\n\nfunc TestSubscriptionAnalyzer(t *testing.T) {\n\tok := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"Subscription\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"ok-sub\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"state\": \"AtLatestKnown\",\n\t\t\t},\n\t\t},\n\t}\n\n\tbad := &unstructured.Unstructured{\n\t\tObject: map[string]any{\n\t\t\t\"apiVersion\": \"operators.coreos.com/v1alpha1\",\n\t\t\t\"kind\":       \"Subscription\",\n\t\t\t\"metadata\": map[string]any{\n\t\t\t\t\"name\":      \"upgrade-sub\",\n\t\t\t\t\"namespace\": \"ns1\",\n\t\t\t},\n\t\t\t\"status\": map[string]any{\n\t\t\t\t\"state\": \"UpgradeAvailable\",\n\t\t\t\t\"conditions\": []interface{}{\n\t\t\t\t\tmap[string]any{\n\t\t\t\t\t\t\"status\":  \"False\",\n\t\t\t\t\t\t\"reason\":  \"CatalogSourcesUnhealthy\",\n\t\t\t\t\t\t\"message\": \"not reachable\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlistKinds := map[schema.GroupVersionResource]string{\n\t\t{Group: \"operators.coreos.com\", Version: \"v1alpha1\", Resource: \"subscriptions\"}: \"SubscriptionList\",\n\t}\n\n\tscheme := runtime.NewScheme()\n\tdc := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, ok, bad)\n\n\ta := common.Analyzer{\n\t\tContext: context.TODO(),\n\t\tClient:  &kubernetes.Client{DynamicClient: dc},\n\t}\n\n\tres, err := (SubscriptionAnalyzer{}).Analyze(a)\n\tif err != nil {\n\t\tt.Fatalf(\"Analyze error: %v\", err)\n\t}\n\n\tif len(res) != 1 {\n\t\tt.Fatalf(\"expected 1 result, got %d\", len(res))\n\t}\n\tif res[0].Kind != \"Subscription\" || !strings.Contains(res[0].Name, \"ns1/upgrade-sub\") {\n\t\tt.Fatalf(\"unexpected result: %#v\", res[0])\n\t}\n\tif len(res[0].Error) == 0 || !strings.Contains(res[0].Error[0].Text, \"CatalogSourcesUnhealthy\") {\n\t\tt.Fatalf(\"expected 'CatalogSourcesUnhealthy' in failure, got %#v\", res[0].Error)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/test_utils.go",
    "content": "package analyzer\n\n// Helper functions for tests\nfunc boolPtr(b bool) *bool {\n\treturn &b\n}\n\nfunc int64Ptr(i int64) *int64 {\n\treturn &i\n}\n"
  },
  {
    "path": "pkg/analyzer/validating_webhook.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\ntype ValidatingWebhookAnalyzer struct{}\n\nfunc (ValidatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tkind := \"ValidatingWebhookConfiguration\"\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind: kind,\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"apps\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tAnalyzerErrorsMetric.DeletePartialMatch(map[string]string{\n\t\t\"analyzer_name\": kind,\n\t})\n\n\tvalidatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, webhookConfig := range validatingWebhooks.Items {\n\t\tfor _, webhook := range webhookConfig.Webhooks {\n\t\t\tvar failures []common.Failure\n\t\t\tif webhook.ClientConfig.Service == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsvc := webhook.ClientConfig.Service\n\t\t\t// Get the service\n\t\t\tservice, err := a.Client.GetClient().CoreV1().Services(svc.Namespace).Get(context.Background(), svc.Name, v1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\t// If the service is not found, we can't check the pods\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"Service %s not found as mapped to by Validating Webhook %s\", svc.Name, webhook.Name),\n\t\t\t\t\tKubernetesDoc: apiDoc.GetApiDocV2(\"spec.webhook.clientConfig.service\"),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: svc.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(svc.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", webhookConfig.Namespace, webhook.Name)] = common.PreAnalysis{\n\t\t\t\t\tValidatingWebhook: webhookConfig,\n\t\t\t\t\tFailureDetails:    failures,\n\t\t\t\t}\n\t\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, webhook.Name, webhookConfig.Namespace).Set(float64(len(failures)))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// When Service selectors are empty we defer to service analyser\n\t\t\tif len(service.Spec.Selector) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Get pods within service\n\t\t\tpods, err := a.Client.GetClient().CoreV1().Pods(svc.Namespace).List(context.Background(), v1.ListOptions{\n\t\t\t\tLabelSelector: util.MapToString(service.Spec.Selector),\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\tif len(pods.Items) == 0 {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"No active pods found within service %s as mapped to by Validating Webhook %s\", svc.Name, webhook.Name),\n\t\t\t\t\tKubernetesDoc: apiDoc.GetApiDocV2(\"spec.webhook.clientConfig.service\"),\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t}\n\t\t\tfor _, pod := range pods.Items {\n\t\t\t\tif pod.Status.Phase != \"Running\" {\n\t\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.webhook\")\n\t\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\t\tText: fmt.Sprintf(\n\t\t\t\t\t\t\t\"Validating Webhook (%s) is pointing to an inactive receiver pod (%s)\",\n\t\t\t\t\t\t\twebhook.Name,\n\t\t\t\t\t\t\tpod.Name,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: webhookConfig.Namespace,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(webhookConfig.Namespace),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: webhook.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(webhook.Name),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tUnmasked: pod.Name,\n\t\t\t\t\t\t\t\tMasked:   util.MaskString(pod.Name),\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\tif len(failures) > 0 {\n\t\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", webhookConfig.Namespace, webhook.Name)] = common.PreAnalysis{\n\t\t\t\t\tValidatingWebhook: webhookConfig,\n\t\t\t\t\tFailureDetails:    failures,\n\t\t\t\t}\n\t\t\t\tAnalyzerErrorsMetric.WithLabelValues(kind, webhook.Name, webhookConfig.Namespace).Set(float64(len(failures)))\n\t\t\t}\n\t\t}\n\t}\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, found := util.GetParent(a.Client, value.ValidatingWebhook.ObjectMeta)\n\t\tif found {\n\t\t\tcurrentAnalysis.ParentObject = parent\n\t\t}\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/validating_webhook_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 analyzer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tadmissionregistrationv1 \"k8s.io/api/admissionregistration/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestValidatingWebhookAnalyzer(t *testing.T) {\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: fake.NewSimpleClientset(\n\t\t\t\t&v1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"Pod1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\tNamespace: \"default\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod1\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service2\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t// No such pod exists in the test namespace\n\t\t\t\t\t\tSelector: map[string]string{\n\t\t\t\t\t\t\t\"pod\": \"Pod2\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&v1.Service{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-service3\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: v1.ServiceSpec{\n\t\t\t\t\t\t// len(service.Spec.Selector) == 0\n\t\t\t\t\t\tSelector: map[string]string{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t&admissionregistrationv1.ValidatingWebhookConfiguration{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:      \"test-validating-webhook-config\",\n\t\t\t\t\t\tNamespace: \"test\",\n\t\t\t\t\t},\n\t\t\t\t\tWebhooks: []admissionregistrationv1.ValidatingWebhook{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Failure: Pointing to an inactive receiver pod\n\t\t\t\t\t\t\tName: \"webhook1\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service1\",\n\t\t\t\t\t\t\t\t\tNamespace: \"default\",\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\t{\n\t\t\t\t\t\t\t// Failure: No active pods found in the test namespace\n\t\t\t\t\t\t\tName: \"webhook2\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service2\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\tName: \"webhook3\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service3\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\t// Failure: Service doesn't exist.\n\t\t\t\t\t\t\tName: \"webhook4\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\t\t\tName:      \"test-service4-doesn't-exist\",\n\t\t\t\t\t\t\t\t\tNamespace: \"test\",\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\t{\n\t\t\t\t\t\t\t// Service is nil.\n\t\t\t\t\t\t\tName:         \"webhook5\",\n\t\t\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{},\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\tContext:   context.Background(),\n\t\tNamespace: \"default\",\n\t}\n\n\tvwAnalyzer := ValidatingWebhookAnalyzer{}\n\tresults, err := vwAnalyzer.Analyze(config)\n\trequire.NoError(t, err)\n\n\t// The results should contain: webhook1, webhook2, and webhook4\n\tresultsLen := 3\n\trequire.Equal(t, resultsLen, len(results))\n}\n\nfunc TestValidatingWebhookAnalyzerLabelSelectorFiltering(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset(\n\t\t&admissionregistrationv1.ValidatingWebhookConfiguration{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"test-validating-webhook-config1\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"app\":     \"validating-webhook\",\n\t\t\t\t\t\"part-of\": \"test\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tWebhooks: []admissionregistrationv1.ValidatingWebhook{\n\t\t\t\t{\n\t\t\t\t\t// Failure: Pointing to an inactive receiver pod\n\t\t\t\t\tName: \"webhook1\",\n\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\tName:      \"test-service1\",\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\t\t&admissionregistrationv1.ValidatingWebhookConfiguration{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"test-validating-webhook-config2\",\n\t\t\t\tNamespace: \"default\",\n\t\t\t},\n\t\t\tWebhooks: []admissionregistrationv1.ValidatingWebhook{\n\t\t\t\t{\n\t\t\t\t\t// Failure: Pointing to an inactive receiver pod\n\t\t\t\t\tName: \"webhook1\",\n\t\t\t\t\tClientConfig: admissionregistrationv1.WebhookClientConfig{\n\t\t\t\t\t\tService: &admissionregistrationv1.ServiceReference{\n\t\t\t\t\t\t\tName:      \"test-service1\",\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\t)\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=validating-webhook\",\n\t}\n\n\tvwAnalyzer := ValidatingWebhookAnalyzer{}\n\tresults, err := vwAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\trequire.Equal(t, 1, len(results))\n\n\tconfig = common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tClient: clientSet,\n\t\t},\n\t\tContext:       context.Background(),\n\t\tNamespace:     \"default\",\n\t\tLabelSelector: \"app=validating-webhook,part-of=test\",\n\t}\n\n\tvwAnalyzer = ValidatingWebhookAnalyzer{}\n\tresults, err = vwAnalyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\trequire.Equal(t, 1, len(results))\n}\n"
  },
  {
    "path": "pkg/cache/azuresa_based.go",
    "content": "package cache\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azidentity\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob\"\n)\n\n// Generate ICache implementation\ntype AzureCache struct {\n\tctx           context.Context\n\tnoCache       bool\n\tcontainerName string\n\tsession       *azblob.Client\n}\n\ntype AzureCacheConfiguration struct {\n\tStorageAccount string `mapstructure:\"storageaccount\" yaml:\"storageaccount,omitempty\"`\n\tContainerName  string `mapstructure:\"container\" yaml:\"container,omitempty\"`\n}\n\nfunc (s *AzureCache) Configure(cacheInfo CacheProvider) error {\n\ts.ctx = context.Background()\n\tif cacheInfo.Azure.ContainerName == \"\" {\n\t\tlog.Fatal(\"Azure Container name not configured\")\n\t}\n\tif cacheInfo.Azure.StorageAccount == \"\" {\n\t\tlog.Fatal(\"Azure Storage account not configured\")\n\t}\n\n\t// We assume that Storage account is already in place\n\tblobUrl := fmt.Sprintf(\"https://%s.blob.core.windows.net/\", cacheInfo.Azure.StorageAccount)\n\tcredential, err := azidentity.NewDefaultAzureCredential(nil)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tclient, err := azblob.NewClient(blobUrl, credential, nil)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// Try to create the blob container\n\t_, err = client.CreateContainer(s.ctx, cacheInfo.Azure.ContainerName, nil)\n\tif err != nil {\n\t\t// TODO: Maybe there is a better way to check this?\n\t\t// docs: https://pkg.go.dev/github.com/Azure/azure-storage-blob-go/azblob\n\t\tif strings.Contains(err.Error(), \"ContainerAlreadyExists\") {\n\t\t\t// do nothing\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t}\n\ts.containerName = cacheInfo.Azure.ContainerName\n\ts.session = client\n\n\treturn nil\n\n}\n\nfunc (s *AzureCache) Store(key string, data string) error {\n\t// Store the object as a new file in the Azure blob storage with data as the content\n\tcacheData := []byte(data)\n\t_, err := s.session.UploadBuffer(s.ctx, s.containerName, key, cacheData, &azblob.UploadBufferOptions{})\n\treturn err\n}\n\nfunc (s *AzureCache) Load(key string) (string, error) {\n\t// Load blob file contents\n\tload, err := s.session.DownloadStream(s.ctx, s.containerName, key, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdata := bytes.Buffer{}\n\tretryReader := load.NewRetryReader(s.ctx, &azblob.RetryReaderOptions{})\n\t_, err = data.ReadFrom(retryReader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := retryReader.Close(); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn data.String(), nil\n}\n\nfunc (s *AzureCache) List() ([]CacheObjectDetails, error) {\n\t// List the files in the blob containerName\n\tfiles := []CacheObjectDetails{}\n\n\tpager := s.session.NewListBlobsFlatPager(s.containerName, &azblob.ListBlobsFlatOptions{\n\t\tInclude: azblob.ListBlobsInclude{Snapshots: false, Versions: false},\n\t})\n\n\tfor pager.More() {\n\t\tresp, err := pager.NextPage(s.ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, blob := range resp.Segment.BlobItems {\n\t\t\tfiles = append(files, CacheObjectDetails{\n\t\t\t\tName:      *blob.Name,\n\t\t\t\tUpdatedAt: *blob.Properties.LastModified,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn files, nil\n}\n\nfunc (s *AzureCache) Remove(key string) error {\n\t_, err := s.session.DeleteBlob(s.ctx, s.containerName, key, &blob.DeleteOptions{})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *AzureCache) Exists(key string) bool {\n\t// Check if the object exists in the blob storage\n\tpager := s.session.NewListBlobsFlatPager(s.containerName, &azblob.ListBlobsFlatOptions{\n\t\tInclude: azblob.ListBlobsInclude{Snapshots: false, Versions: false},\n\t})\n\n\tfor pager.More() {\n\t\tresp, err := pager.NextPage(s.ctx)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, blob := range resp.Segment.BlobItems {\n\t\t\tif *blob.Name == key {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (s *AzureCache) IsCacheDisabled() bool {\n\treturn s.noCache\n}\n\nfunc (s *AzureCache) GetName() string {\n\treturn \"azure\"\n}\n\nfunc (s *AzureCache) DisableCache() {\n\ts.noCache = true\n}\n"
  },
  {
    "path": "pkg/cache/cache.go",
    "content": "package cache\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/viper\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nvar (\n\ttypes = []ICache{\n\t\t&AzureCache{},\n\t\t&FileBasedCache{},\n\t\t&GCSCache{},\n\t\t&S3Cache{},\n\t\t&InterplexCache{},\n\t}\n)\n\ntype ICache interface {\n\tConfigure(cacheInfo CacheProvider) error\n\tStore(key string, data string) error\n\tLoad(key string) (string, error)\n\tList() ([]CacheObjectDetails, error)\n\tRemove(key string) error\n\tExists(key string) bool\n\tIsCacheDisabled() bool\n\tGetName() string\n\tDisableCache()\n}\n\nfunc New(cacheType string) ICache {\n\tfor _, t := range types {\n\t\tif cacheType == t.GetName() {\n\t\t\treturn t\n\t\t}\n\t}\n\treturn &FileBasedCache{}\n}\n\nfunc ParseCacheConfiguration() (CacheProvider, error) {\n\tvar cacheInfo CacheProvider\n\terr := viper.UnmarshalKey(\"cache\", &cacheInfo)\n\tif err != nil {\n\t\treturn cacheInfo, err\n\t}\n\treturn cacheInfo, nil\n}\n\nfunc NewCacheProvider(cacheType, bucketname, region, endpoint, storageAccount, containerName, projectId string, insecure bool) (CacheProvider, error) {\n\tcProvider := CacheProvider{}\n\n\tswitch {\n\tcase cacheType == \"azure\":\n\t\tcProvider.Azure.ContainerName = containerName\n\t\tcProvider.Azure.StorageAccount = storageAccount\n\t\tcProvider.CurrentCacheType = \"azure\"\n\tcase cacheType == \"gcs\":\n\t\tcProvider.GCS.BucketName = bucketname\n\t\tcProvider.GCS.ProjectId = projectId\n\t\tcProvider.GCS.Region = region\n\t\tcProvider.CurrentCacheType = \"gcs\"\n\tcase cacheType == \"s3\":\n\t\tcProvider.S3.BucketName = bucketname\n\t\tcProvider.S3.Region = region\n\t\tcProvider.S3.Endpoint = endpoint\n\t\tcProvider.S3.InsecureSkipVerify = insecure\n\t\tcProvider.CurrentCacheType = \"s3\"\n\tcase cacheType == \"interplex\":\n\t\tcProvider.Interplex.ConnectionString = endpoint\n\t\tcProvider.CurrentCacheType = \"interplex\"\n\tdefault:\n\t\treturn CacheProvider{}, status.Error(codes.Internal, fmt.Sprintf(\"%s is not a valid option\", cacheType))\n\t}\n\n\tcache := New(cacheType)\n\terr := cache.Configure(cProvider)\n\tif err != nil {\n\t\treturn CacheProvider{}, err\n\t}\n\treturn cProvider, nil\n}\n\n// If we have set a remote cache, return the remote cache configuration\nfunc GetCacheConfiguration() (ICache, error) {\n\tcacheInfo, err := ParseCacheConfiguration()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cache ICache\n\tswitch {\n\tcase cacheInfo.CurrentCacheType == \"gcs\":\n\t\tcache = &GCSCache{}\n\tcase cacheInfo.CurrentCacheType == \"azure\":\n\t\tcache = &AzureCache{}\n\tcase cacheInfo.CurrentCacheType == \"s3\":\n\t\tcache = &S3Cache{}\n\tcase cacheInfo.CurrentCacheType == \"interplex\":\n\t\tcache = &InterplexCache{}\n\tdefault:\n\t\tcache = &FileBasedCache{}\n\t}\n\terr_config := cache.Configure(cacheInfo)\n\treturn cache, err_config\n}\n\nfunc AddRemoteCache(cacheInfo CacheProvider) error {\n\n\tviper.Set(\"cache\", cacheInfo)\n\n\terr := viper.WriteConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc RemoveRemoteCache() error {\n\tvar cacheInfo CacheProvider\n\terr := viper.UnmarshalKey(\"cache\", &cacheInfo)\n\tif err != nil {\n\t\treturn status.Error(codes.Internal, \"cache unmarshal\")\n\t}\n\n\tcacheInfo = CacheProvider{}\n\tviper.Set(\"cache\", cacheInfo)\n\terr = viper.WriteConfig()\n\tif err != nil {\n\t\treturn status.Error(codes.Internal, \"unable to write config\")\n\t}\n\n\treturn nil\n\n}\n"
  },
  {
    "path": "pkg/cache/cache_test.go",
    "content": "package cache\n\nimport (\n    \"os\"\n    \"testing\"\n\n    \"github.com/spf13/viper\"\n    \"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewReturnsExpectedCache(t *testing.T) {\n    require.IsType(t, &FileBasedCache{}, New(\"file\"))\n    require.IsType(t, &AzureCache{}, New(\"azure\"))\n    require.IsType(t, &GCSCache{}, New(\"gcs\"))\n    require.IsType(t, &S3Cache{}, New(\"s3\"))\n    require.IsType(t, &InterplexCache{}, New(\"interplex\"))\n    // default fallback\n    require.IsType(t, &FileBasedCache{}, New(\"unknown\"))\n}\n\nfunc TestNewCacheProvider_InterplexAndInvalid(t *testing.T) {\n    // valid: interplex\n    cp, err := NewCacheProvider(\"interplex\", \"\", \"\", \"localhost:1\", \"\", \"\", \"\", false)\n    require.NoError(t, err)\n    require.Equal(t, \"interplex\", cp.CurrentCacheType)\n    require.Equal(t, \"localhost:1\", cp.Interplex.ConnectionString)\n\n    // invalid type\n    _, err = NewCacheProvider(\"not-a-type\", \"\", \"\", \"\", \"\", \"\", \"\", false)\n    require.Error(t, err)\n}\n\nfunc TestAddRemoveRemoteCacheAndGet(t *testing.T) {\n    // isolate viper with temp config file\n    tmpFile, err := os.CreateTemp(\"\", \"k8sgpt-cache-config-*.yaml\")\n    require.NoError(t, err)\n    defer func() {\n        _ = os.Remove(tmpFile.Name())\n    }()\n    viper.Reset()\n    viper.SetConfigFile(tmpFile.Name())\n\n    // add interplex remote cache\n    cp := CacheProvider{}\n    cp.CurrentCacheType = \"interplex\"\n    cp.Interplex.ConnectionString = \"localhost:1\"\n    require.NoError(t, AddRemoteCache(cp))\n\n    // read back via GetCacheConfiguration\n    c, err := GetCacheConfiguration()\n    require.NoError(t, err)\n    require.IsType(t, &InterplexCache{}, c)\n\n    // remove remote cache\n    require.NoError(t, RemoveRemoteCache())\n    // now default should be file-based\n    c2, err := GetCacheConfiguration()\n    require.NoError(t, err)\n    require.IsType(t, &FileBasedCache{}, c2)\n}"
  },
  {
    "path": "pkg/cache/file_based.go",
    "content": "package cache\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/adrg/xdg\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n)\n\nvar _ (ICache) = (*FileBasedCache)(nil)\n\ntype FileBasedCache struct {\n\tnoCache bool\n}\n\nfunc (f *FileBasedCache) Configure(cacheInfo CacheProvider) error {\n\treturn nil\n}\n\nfunc (f *FileBasedCache) IsCacheDisabled() bool {\n\treturn f.noCache\n}\n\nfunc (*FileBasedCache) List() ([]CacheObjectDetails, error) {\n\tpath, err := xdg.CacheFile(\"k8sgpt\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfiles, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar result []CacheObjectDetails\n\tfor _, file := range files {\n\t\tinfo, err := file.Info()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult = append(result, CacheObjectDetails{\n\t\t\tName:      file.Name(),\n\t\t\tUpdatedAt: info.ModTime(),\n\t\t})\n\t}\n\n\treturn result, nil\n}\n\nfunc (*FileBasedCache) Exists(key string) bool {\n\tpath, err := xdg.CacheFile(filepath.Join(\"k8sgpt\", key))\n\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"warning: error while testing if cache key exists:\", err)\n\t\treturn false\n\t}\n\n\texists, err := util.FileExists(path)\n\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, \"warning: error while testing if cache key exists:\", err)\n\t\treturn false\n\t}\n\n\treturn exists\n}\n\nfunc (*FileBasedCache) Load(key string) (string, error) {\n\tpath, err := xdg.CacheFile(filepath.Join(\"k8sgpt\", key))\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdata, err := os.ReadFile(path)\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(data), nil\n}\n\nfunc (*FileBasedCache) Remove(key string) error {\n\tpath, err := xdg.CacheFile(filepath.Join(\"k8sgpt\", key))\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := os.Remove(path); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (*FileBasedCache) Store(key string, data string) error {\n\tpath, err := xdg.CacheFile(filepath.Join(\"k8sgpt\", key))\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn os.WriteFile(path, []byte(data), 0600)\n}\n\nfunc (s *FileBasedCache) GetName() string {\n\treturn \"file\"\n}\n\nfunc (s *FileBasedCache) DisableCache() {\n\ts.noCache = true\n}\n"
  },
  {
    "path": "pkg/cache/file_based_test.go",
    "content": "package cache\n\nimport (\n    \"os\"\n    \"path/filepath\"\n    \"testing\"\n\n    \"github.com/adrg/xdg\"\n    \"github.com/stretchr/testify/require\"\n)\n\n// withTempCacheHome sets XDG_CACHE_HOME to a temp dir for test isolation.\nfunc withTempCacheHome(t *testing.T) func() {\n    t.Helper()\n    tmp, err := os.MkdirTemp(\"\", \"k8sgpt-cache-test-*\")\n    require.NoError(t, err)\n    old := os.Getenv(\"XDG_CACHE_HOME\")\n    require.NoError(t, os.Setenv(\"XDG_CACHE_HOME\", tmp))\n    return func() {\n        _ = os.Setenv(\"XDG_CACHE_HOME\", old)\n        _ = os.RemoveAll(tmp)\n    }\n}\n\nfunc TestFileBasedCache_BasicOps(t *testing.T) {\n    cleanup := withTempCacheHome(t)\n    defer cleanup()\n\n    c := &FileBasedCache{}\n    // Configure should be a no-op\n    require.NoError(t, c.Configure(CacheProvider{}))\n    require.Equal(t, \"file\", c.GetName())\n    require.False(t, c.IsCacheDisabled())\n    c.DisableCache()\n    require.True(t, c.IsCacheDisabled())\n\n    key := \"testkey\"\n    data := \"hello\"\n\n    // Store\n    require.NoError(t, c.Store(key, data))\n\n    // Exists\n    require.True(t, c.Exists(key))\n\n    // Load\n    got, err := c.Load(key)\n    require.NoError(t, err)\n    require.Equal(t, data, got)\n\n    // List should include our key file\n    items, err := c.List()\n    require.NoError(t, err)\n    // ensure at least one item and that one matches our key\n    found := false\n    for _, it := range items {\n        if it.Name == key {\n            found = true\n            break\n        }\n    }\n    require.True(t, found)\n\n    // Remove\n    require.NoError(t, c.Remove(key))\n    require.False(t, c.Exists(key))\n}\n\nfunc TestFileBasedCache_PathShape(t *testing.T) {\n    cleanup := withTempCacheHome(t)\n    defer cleanup()\n    // Verify xdg.CacheFile path shape (directory and filename)\n    p, err := xdg.CacheFile(filepath.Join(\"k8sgpt\", \"abc\"))\n    require.NoError(t, err)\n    require.Equal(t, \"abc\", filepath.Base(p))\n    require.Contains(t, p, \"k8sgpt\")\n}"
  },
  {
    "path": "pkg/cache/gcs_based.go",
    "content": "package cache\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log\"\n\n\t\"cloud.google.com/go/storage\"\n\t\"google.golang.org/api/iterator\"\n)\n\ntype GCSCache struct {\n\tctx        context.Context\n\tnoCache    bool\n\tbucketName string\n\tprojectId  string\n\tregion     string\n\tsession    *storage.Client\n}\n\ntype GCSCacheConfiguration struct {\n\tProjectId  string `mapstructure:\"projectid\" yaml:\"projectid,omitempty\"`\n\tRegion     string `mapstructure:\"region\" yaml:\"region,omitempty\"`\n\tBucketName string `mapstructure:\"bucketname\" yaml:\"bucketname,omitempty\"`\n}\n\nfunc (s *GCSCache) Configure(cacheInfo CacheProvider) error {\n\ts.ctx = context.Background()\n\tif cacheInfo.GCS.BucketName == \"\" {\n\t\tlog.Fatal(\"Bucket name not configured\")\n\t}\n\tif cacheInfo.GCS.Region == \"\" {\n\t\tlog.Fatal(\"Region not configured\")\n\t}\n\tif cacheInfo.GCS.ProjectId == \"\" {\n\t\tlog.Fatal(\"ProjectID not configured\")\n\t}\n\ts.bucketName = cacheInfo.GCS.BucketName\n\ts.projectId = cacheInfo.GCS.ProjectId\n\ts.region = cacheInfo.GCS.Region\n\tstorageClient, err := storage.NewClient(s.ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = storageClient.Bucket(s.bucketName).Attrs(s.ctx)\n\tif err == storage.ErrBucketNotExist {\n\t\terr = storageClient.Bucket(s.bucketName).Create(s.ctx, s.projectId, &storage.BucketAttrs{\n\t\t\tLocation: s.region,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\ts.session = storageClient\n\treturn nil\n}\n\nfunc (s *GCSCache) Store(key string, data string) error {\n\twc := s.session.Bucket(s.bucketName).Object(key).NewWriter(s.ctx)\n\n\tif _, err := wc.Write([]byte(data)); err != nil {\n\t\treturn err\n\t}\n\n\tif err := wc.Close(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *GCSCache) Load(key string) (string, error) {\n\treader, err := s.session.Bucket(s.bucketName).Object(key).NewReader(s.ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer reader.Close()\n\n\tdata, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(data), nil\n}\n\nfunc (s *GCSCache) Remove(key string) error {\n\tbucketClient := s.session.Bucket(s.bucketName)\n\tobj := bucketClient.Object(key)\n\tif err := obj.Delete(s.ctx); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *GCSCache) List() ([]CacheObjectDetails, error) {\n\tvar files []CacheObjectDetails\n\n\titems := s.session.Bucket(s.bucketName).Objects(s.ctx, nil)\n\tfor {\n\t\tattrs, err := items.Next()\n\t\tif err == iterator.Done {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfiles = append(files, CacheObjectDetails{\n\t\t\tName:      attrs.Name,\n\t\t\tUpdatedAt: attrs.Updated,\n\t\t})\n\t}\n\treturn files, nil\n}\n\nfunc (s *GCSCache) Exists(key string) bool {\n\tobj := s.session.Bucket(s.bucketName).Object(key)\n\t_, err := obj.Attrs(s.ctx)\n\treturn err == nil\n}\n\nfunc (s *GCSCache) IsCacheDisabled() bool {\n\treturn s.noCache\n}\n\nfunc (s *GCSCache) GetName() string {\n\treturn \"gcs\"\n}\n\nfunc (s *GCSCache) DisableCache() {\n\ts.noCache = true\n}\n"
  },
  {
    "path": "pkg/cache/interplex_based.go",
    "content": "package cache\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\trpc \"buf.build/gen/go/interplex-ai/schemas/grpc/go/protobuf/schema/v1/schemav1grpc\"\n\tschemav1 \"buf.build/gen/go/interplex-ai/schemas/protocolbuffers/go/protobuf/schema/v1\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\nvar _ ICache = (*InterplexCache)(nil)\n\ntype InterplexCache struct {\n\tconfiguration      InterplexCacheConfiguration\n\tclient             InterplexClient\n\tcacheServiceClient rpc.CacheServiceClient\n\tnoCache            bool\n}\n\ntype InterplexCacheConfiguration struct {\n\tConnectionString string `mapstructure:\"connectionString\" yaml:\"connectionString,omitempty\"`\n}\n\ntype InterplexClient struct {\n}\n\nfunc (c *InterplexCache) Configure(cacheInfo CacheProvider) error {\n\n\tif cacheInfo.Interplex.ConnectionString == \"\" {\n\t\treturn errors.New(\"connection string is required\")\n\t}\n\tc.configuration.ConnectionString = cacheInfo.Interplex.ConnectionString\n\treturn nil\n}\n\nfunc (c *InterplexCache) Store(key string, data string) error {\n\n\tif os.Getenv(\"INTERPLEX_LOCAL_MODE\") != \"\" {\n\t\tc.configuration.ConnectionString = \"localhost:8084\"\n\t}\n\n\tconn, err := grpc.NewClient(c.configuration.ConnectionString, grpc.WithInsecure(), grpc.WithBlock())\n\tdefer conn.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\tserviceClient := rpc.NewCacheServiceClient(conn)\n\tc.cacheServiceClient = serviceClient\n\treq := schemav1.SetRequest{\n\t\tKey:   key,\n\t\tValue: data,\n\t}\n\t_, err = c.cacheServiceClient.Set(context.Background(), &req)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *InterplexCache) Load(key string) (string, error) {\n\tif os.Getenv(\"INTERPLEX_LOCAL_MODE\") != \"\" {\n\t\tc.configuration.ConnectionString = \"localhost:8084\"\n\t}\n\n\tconn, err := grpc.NewClient(c.configuration.ConnectionString, grpc.WithInsecure(), grpc.WithBlock())\n\tdefer conn.Close()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tserviceClient := rpc.NewCacheServiceClient(conn)\n\tc.cacheServiceClient = serviceClient\n\treq := schemav1.GetRequest{\n\t\tKey: key,\n\t}\n\tresp, err := c.cacheServiceClient.Get(context.Background(), &req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resp.Value, nil\n}\n\nfunc (c *InterplexCache) List() ([]CacheObjectDetails, error) {\n\t// Not implemented for Interplex cache\n\treturn []CacheObjectDetails{}, nil\n}\n\nfunc (c *InterplexCache) Remove(key string) error {\n\tif os.Getenv(\"INTERPLEX_LOCAL_MODE\") != \"\" {\n\t\tc.configuration.ConnectionString = \"localhost:8084\"\n\t}\n\n\tconn, err := grpc.NewClient(c.configuration.ConnectionString, grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := conn.Close(); err != nil {\n\t\t\t// Log the error but don't return it since this is a deferred function\n\t\t\tfmt.Printf(\"Error closing connection: %v\\n\", err)\n\t\t}\n\t}()\n\n\tserviceClient := rpc.NewCacheServiceClient(conn)\n\tc.cacheServiceClient = serviceClient\n\treq := schemav1.DeleteRequest{\n\t\tKey: key,\n\t}\n\t_, err = c.cacheServiceClient.Delete(context.Background(), &req)\n\treturn err\n}\n\nfunc (c *InterplexCache) Exists(key string) bool {\n\t_, err := c.Load(key)\n\treturn err == nil\n}\n\nfunc (c *InterplexCache) IsCacheDisabled() bool {\n\treturn c.noCache\n}\n\nfunc (c *InterplexCache) GetName() string {\n\treturn \"interplex\"\n}\n\nfunc (c *InterplexCache) DisableCache() {\n\tc.noCache = true\n}\n"
  },
  {
    "path": "pkg/cache/interplex_based_test.go",
    "content": "package cache\n\nimport (\n\trpc \"buf.build/gen/go/interplex-ai/schemas/grpc/go/protobuf/schema/v1/schemav1grpc\"\n\tschemav1 \"buf.build/gen/go/interplex-ai/schemas/protocolbuffers/go/protobuf/schema/v1\"\n\t\"context\"\n\t\"errors\"\n\t\"google.golang.org/grpc\"\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestInterplexCache(t *testing.T) {\n\tcache := &InterplexCache{\n\t\tconfiguration: InterplexCacheConfiguration{\n\t\t\tConnectionString: \"localhost:50051\",\n\t\t},\n\t}\n\n\t// Mock GRPC server setup\n\terrChan := make(chan error, 1)\n\tgo func() {\n\t\tlis, err := net.Listen(\"tcp\", \":50051\")\n\t\tif err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t\ts := grpc.NewServer()\n\t\trpc.RegisterCacheServiceServer(s, &mockCacheService{})\n\t\tif err := s.Serve(lis); err != nil {\n\t\t\terrChan <- err\n\t\t\treturn\n\t\t}\n\t}()\n\n\t// Check if server startup failed\n\tselect {\n\tcase err := <-errChan:\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to start mock server: %v\", err)\n\t\t}\n\tdefault:\n\t\t// Server started successfully\n\t}\n\n\tt.Run(\"TestStore\", func(t *testing.T) {\n\t\terr := cache.Store(\"key1\", \"value1\")\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error storing value: %v\", err)\n\t\t}\n\t})\n\n\tt.Run(\"TestLoad\", func(t *testing.T) {\n\t\tvalue, err := cache.Load(\"key1\")\n\t\tif err != nil {\n\t\t\tt.Errorf(\"Error loading value: %v\", err)\n\t\t}\n\t\tif value != \"value1\" {\n\t\t\tt.Errorf(\"Expected value1, got %v\", value)\n\t\t}\n\t})\n\n\tt.Run(\"TestExists\", func(t *testing.T) {\n\t\texists := cache.Exists(\"key1\")\n\t\tif !exists {\n\t\t\tt.Errorf(\"Expected key1 to exist\")\n\t\t}\n\t})\n}\n\ntype mockCacheService struct {\n\trpc.UnimplementedCacheServiceServer\n\tdata map[string]string\n}\n\nfunc (m *mockCacheService) Set(ctx context.Context, req *schemav1.SetRequest) (*schemav1.SetResponse, error) {\n\tif m.data == nil {\n\t\tm.data = make(map[string]string)\n\t}\n\tm.data[req.Key] = req.Value\n\treturn &schemav1.SetResponse{}, nil\n}\n\nfunc (m *mockCacheService) Get(ctx context.Context, req *schemav1.GetRequest) (*schemav1.GetResponse, error) {\n\tvalue, exists := m.data[req.Key]\n\tif !exists {\n\t\treturn nil, errors.New(\"key not found\")\n\t}\n\treturn &schemav1.GetResponse{Value: value}, nil\n}\n"
  },
  {
    "path": "pkg/cache/s3_based.go",
    "content": "package cache\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/s3\"\n)\n\n// Generate ICache implementation\ntype S3Cache struct {\n\tnoCache    bool\n\tbucketName string\n\tsession    *s3.S3\n}\n\ntype S3CacheConfiguration struct {\n\tRegion             string `mapstructure:\"region\" yaml:\"region,omitempty\"`\n\tBucketName         string `mapstructure:\"bucketname\" yaml:\"bucketname,omitempty\"`\n\tEndpoint           string `mapstructure:\"endpoint\" yaml:\"endpoint,omitempty\"`\n\tInsecureSkipVerify bool   `mapstructure:\"insecure\" yaml:\"insecure,omitempty\"`\n}\n\nfunc (s *S3Cache) Configure(cacheInfo CacheProvider) error {\n\tif cacheInfo.S3.BucketName == \"\" {\n\t\treturn errors.New(\"bucket name not configured\")\n\t}\n\ts.bucketName = cacheInfo.S3.BucketName\n\n\tsess, err := session.NewSessionWithOptions(session.Options{\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t\tConfig: aws.Config{\n\t\t\tRegion: aws.String(cacheInfo.S3.Region),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn errors.New(\"failed to create AWS session; please check your AWS credentials and configuration: \" + err.Error())\n\t}\n\tif cacheInfo.S3.Endpoint != \"\" {\n\t\tsess.Config.Endpoint = &cacheInfo.S3.Endpoint\n\t\tsess.Config.S3ForcePathStyle = aws.Bool(true)\n\t\ttransport := &http.Transport{\n\t\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: cacheInfo.S3.InsecureSkipVerify},\n\t\t}\n\t\tcustomClient := &http.Client{Transport: transport}\n\t\tsess.Config.HTTPClient = customClient\n\t}\n\n\ts3Client := s3.New(sess)\n\n\t// Check if the bucket exists, if not create it\n\t_, err = s3Client.HeadBucket(&s3.HeadBucketInput{\n\t\tBucket: aws.String(cacheInfo.S3.BucketName),\n\t})\n\tif err != nil {\n\t\t// Check for AWS credentials error\n\t\tif strings.Contains(err.Error(), \"InvalidAccessKeyId\") || strings.Contains(err.Error(), \"SignatureDoesNotMatch\") || strings.Contains(err.Error(), \"NoCredentialProviders\") {\n\t\t\treturn errors.New(\"aws credentials are invalid or missing; please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config\")\n\t\t}\n\t\t_, err = s3Client.CreateBucket(&s3.CreateBucketInput{\n\t\t\tBucket: aws.String(cacheInfo.S3.BucketName),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\ts.session = s3Client\n\treturn nil\n}\n\nfunc (s *S3Cache) Store(key string, data string) error {\n\t// Store the object as a new file in the bucket with data as the content\n\t_, err := s.session.PutObject(&s3.PutObjectInput{\n\t\tBody:   aws.ReadSeekCloser(bytes.NewReader([]byte(data))),\n\t\tBucket: aws.String(s.bucketName),\n\t\tKey:    aws.String(key),\n\t})\n\treturn err\n\n}\n\nfunc (s *S3Cache) Remove(key string) error {\n\t_, err := s.session.DeleteObject(&s3.DeleteObjectInput{\n\t\tBucket: &s.bucketName,\n\t\tKey:    aws.String(key),\n\t})\n\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *S3Cache) Load(key string) (string, error) {\n\n\t// Retrieve the object from the bucket and load it into a string\n\tresult, err := s.session.GetObject(&s3.GetObjectInput{\n\t\tBucket: aws.String(s.bucketName),\n\t\tKey:    aws.String(key),\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbuf := new(bytes.Buffer)\n\t_, err_read := buf.ReadFrom(result.Body)\n\tresult.Body.Close()\n\treturn buf.String(), err_read\n}\n\nfunc (s *S3Cache) List() ([]CacheObjectDetails, error) {\n\n\t// List the files in the bucket\n\tresult, err := s.session.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(s.bucketName)})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar keys []CacheObjectDetails\n\tfor _, item := range result.Contents {\n\t\tkeys = append(keys, CacheObjectDetails{\n\t\t\tName:      *item.Key,\n\t\t\tUpdatedAt: *item.LastModified,\n\t\t})\n\t}\n\n\treturn keys, nil\n}\n\nfunc (s *S3Cache) Exists(key string) bool {\n\t// Check if the object exists in the bucket\n\t_, err := s.session.HeadObject(&s3.HeadObjectInput{\n\t\tBucket: aws.String(s.bucketName),\n\t\tKey:    aws.String(key),\n\t})\n\treturn err == nil\n\n}\n\nfunc (s *S3Cache) IsCacheDisabled() bool {\n\treturn s.noCache\n}\n\nfunc (s *S3Cache) GetName() string {\n\treturn \"s3\"\n}\n\nfunc (s *S3Cache) DisableCache() {\n\ts.noCache = true\n}\n"
  },
  {
    "path": "pkg/cache/types.go",
    "content": "package cache\n\nimport \"time\"\n\ntype CacheProvider struct {\n\tCurrentCacheType string                      `mapstructure:\"currentCacheType\" yaml:\"currentCacheType\"`\n\tGCS              GCSCacheConfiguration       `mapstructure:\"gcs\" yaml:\"gcs,omitempty\"`\n\tAzure            AzureCacheConfiguration     `mapstructure:\"azure\" yaml:\"azure,omitempty\"`\n\tS3               S3CacheConfiguration        `mapstructure:\"s3\" yaml:\"s3,omitempty\"`\n\tInterplex        InterplexCacheConfiguration `mapstructure:\"interplex\" yaml:\"interplex,omitempty\"`\n}\n\ntype CacheObjectDetails struct {\n\tName      string\n\tUpdatedAt time.Time\n}\n"
  },
  {
    "path": "pkg/common/types.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 common\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\topenapi_v2 \"github.com/google/gnostic/openapiv2\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\tkeda \"github.com/kedacore/keda/v2/apis/keda/v1alpha1\"\n\tkyverno \"github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2\"\n\tregv1 \"k8s.io/api/admissionregistration/v1\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tautov2 \"k8s.io/api/autoscaling/v2\"\n\tv1 \"k8s.io/api/core/v1\"\n\tnetworkv1 \"k8s.io/api/networking/v1\"\n\tpolicyv1 \"k8s.io/api/policy/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tgtwapi \"sigs.k8s.io/gateway-api/apis/v1\"\n)\n\ntype IAnalyzer interface {\n\tAnalyze(analysis Analyzer) ([]Result, error)\n}\n\ntype Analyzer struct {\n\tClient        *kubernetes.Client\n\tContext       context.Context\n\tNamespace     string\n\tLabelSelector string\n\tAIClient      ai.IAI\n\tPreAnalysis   map[string]PreAnalysis\n\tResults       []Result\n\tOpenapiSchema *openapi_v2.Document\n}\n\ntype PreAnalysis struct {\n\tPod                      v1.Pod\n\tFailureDetails           []Failure\n\tDeployment               appsv1.Deployment\n\tReplicaSet               appsv1.ReplicaSet\n\tPersistentVolumeClaim    v1.PersistentVolumeClaim\n\tEndpoint                 v1.Endpoints\n\tIngress                  networkv1.Ingress\n\tHorizontalPodAutoscalers autov2.HorizontalPodAutoscaler\n\tPodDisruptionBudget      policyv1.PodDisruptionBudget\n\tStatefulSet              appsv1.StatefulSet\n\tNetworkPolicy            networkv1.NetworkPolicy\n\tNode                     v1.Node\n\tValidatingWebhook        regv1.ValidatingWebhookConfiguration\n\tMutatingWebhook          regv1.MutatingWebhookConfiguration\n\tGatewayClass             gtwapi.GatewayClass\n\tGateway                  gtwapi.Gateway\n\tHTTPRoute                gtwapi.HTTPRoute\n\t// Integrations\n\tScaledObject               keda.ScaledObject\n\tKyvernoPolicyReport        kyverno.PolicyReport\n\tKyvernoClusterPolicyReport kyverno.ClusterPolicyReport\n\tCatalog                    ClusterCatalog\n\tExtension                  ClusterExtension\n}\n\ntype Result struct {\n\tKind         string    `json:\"kind\"`\n\tName         string    `json:\"name\"`\n\tError        []Failure `json:\"error\"`\n\tDetails      string    `json:\"details\"`\n\tParentObject string    `json:\"parentObject\"`\n}\n\ntype AnalysisStats struct {\n\tAnalyzer     string        `json:\"analyzer\"`\n\tDurationTime time.Duration `json:\"durationTime\"`\n}\n\ntype Failure struct {\n\tText          string\n\tKubernetesDoc string\n\tSensitive     []Sensitive\n}\n\ntype Sensitive struct {\n\tUnmasked string\n\tMasked   string\n}\n\ntype (\n\tSourceType                  string\n\tAvailabilityMode            string\n\tUpgradeConstraintPolicy     string\n\tCRDUpgradeSafetyEnforcement string\n)\n\ntype ClusterCatalog struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata\"`\n\tSpec              ClusterCatalogSpec   `json:\"spec\"`\n\tStatus            ClusterCatalogStatus `json:\"status,omitempty\"`\n}\ntype ClusterCatalogList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata\"`\n\tItems           []ClusterCatalog `json:\"items\"`\n}\n\ntype ClusterCatalogSpec struct {\n\tSource CatalogSource `json:\"source\"`\n\n\tPriority int32 `json:\"priority\"`\n\n\tAvailabilityMode AvailabilityMode `json:\"availabilityMode,omitempty\"`\n}\n\ntype ClusterCatalogStatus struct {\n\tConditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\tResolvedSource *ResolvedCatalogSource `json:\"resolvedSource,omitempty\"`\n\n\tURLs         *ClusterCatalogURLs `json:\"urls,omitempty\"`\n\tLastUnpacked *metav1.Time        `json:\"lastUnpacked,omitempty\"`\n}\n\ntype ClusterCatalogURLs struct {\n\tBase string `json:\"base\"`\n}\ntype CatalogSource struct {\n\tType  SourceType   `json:\"type\"`\n\tImage *ImageSource `json:\"image,omitempty\"`\n}\ntype ResolvedCatalogSource struct {\n\tType  SourceType           `json:\"type\"`\n\tImage *ResolvedImageSource `json:\"image\"`\n}\ntype ResolvedImageSource struct {\n\tRef string `json:\"ref\"`\n}\n\ntype ImageSource struct {\n\tRef                 string `json:\"ref\"`\n\tPollIntervalMinutes *int   `json:\"pollIntervalMinutes,omitempty\"`\n}\n\ntype ClusterExtension struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\tSpec              ClusterExtensionSpec   `json:\"spec,omitempty\"`\n\tStatus            ClusterExtensionStatus `json:\"status,omitempty\"`\n}\n\ntype ClusterExtensionSpec struct {\n\tNamespace      string                         `json:\"namespace\"`\n\tServiceAccount ServiceAccountReference        `json:\"serviceAccount\"`\n\tSource         SourceConfig                   `json:\"source\"`\n\tInstall        *ClusterExtensionInstallConfig `json:\"install,omitempty\"`\n}\n\ntype ClusterExtensionInstallConfig struct {\n\tPreflight *PreflightConfig `json:\"preflight,omitempty\"`\n}\n\ntype PreflightConfig struct {\n\tCRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:\"crdUpgradeSafety\"`\n}\n\ntype CRDUpgradeSafetyPreflightConfig struct {\n\tEnforcement CRDUpgradeSafetyEnforcement `json:\"enforcement\"`\n}\n\ntype ServiceAccountReference struct {\n\tName string `json:\"name\"`\n}\n\ntype SourceConfig struct {\n\tSourceType string         `json:\"sourceType\"`\n\tCatalog    *CatalogFilter `json:\"catalog,omitempty\"`\n}\n\ntype CatalogFilter struct {\n\tPackageName             string                  `json:\"packageName\"`\n\tVersion                 string                  `json:\"version,omitempty\"`\n\tChannels                []string                `json:\"channels,omitempty\"`\n\tSelector                *metav1.LabelSelector   `json:\"selector,omitempty\"`\n\tUpgradeConstraintPolicy UpgradeConstraintPolicy `json:\"upgradeConstraintPolicy,omitempty\"`\n}\n\ntype ClusterExtensionStatus struct {\n\tConditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\tInstall *ClusterExtensionInstallStatus `json:\"install,omitempty\"`\n}\n\ntype ClusterExtensionInstallStatus struct {\n\tBundle BundleMetadata `json:\"bundle\"`\n}\n\ntype BundleMetadata struct {\n\tName    string `json:\"name\"`\n\tVersion string `json:\"version\"`\n}\n"
  },
  {
    "path": "pkg/custom/client.go",
    "content": "package custom\n\nimport (\n\trpc \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc\"\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n)\n\ntype Client struct {\n\tc              *grpc.ClientConn\n\tanalyzerClient rpc.CustomAnalyzerServiceClient\n}\n\nfunc NewClient(c Connection) (*Client, error) {\n\n\t//nolint:staticcheck // Ignoring SA1019 for compatibility reasons\n\tconn, err := grpc.Dial(fmt.Sprintf(\"%s:%s\", c.Url, c.Port), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclient := rpc.NewCustomAnalyzerServiceClient(conn)\n\treturn &Client{\n\t\tc:              conn,\n\t\tanalyzerClient: client,\n\t}, nil\n}\n\nfunc (cli *Client) Run() (common.Result, error) {\n\tvar result common.Result\n\treq := &schemav1.RunRequest{}\n\tres, err := cli.analyzerClient.Run(context.Background(), req)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\tif res.Result != nil {\n\n\t\t// We should refactor this, because Error and Failure do not map 1:1 from K8sGPT/schema\n\t\tvar errorsFound []common.Failure\n\t\tfor _, e := range res.Result.Error {\n\t\t\terrorsFound = append(errorsFound, common.Failure{\n\t\t\t\tText: e.Text,\n\t\t\t\t// TODO: Support sensitive data\n\t\t\t})\n\t\t}\n\n\t\tresult.Name = res.Result.Name\n\t\tresult.Kind = res.Result.Kind\n\t\tresult.Details = res.Result.Details\n\t\tresult.ParentObject = res.Result.ParentObject\n\t\tresult.Error = errorsFound\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/custom/client_test.go",
    "content": "package custom\n\nimport (\n    \"context\"\n    \"testing\"\n\n    schemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n    \"github.com/stretchr/testify/require\"\n    \"google.golang.org/grpc\"\n)\n\n// mockAnalyzerClient implements rpc.CustomAnalyzerServiceClient for testing\ntype mockAnalyzerClient struct{\n    resp *schemav1.RunResponse\n    err error\n}\n\nfunc (m *mockAnalyzerClient) Run(ctx context.Context, in *schemav1.RunRequest, opts ...grpc.CallOption) (*schemav1.RunResponse, error) {\n    return m.resp, m.err\n}\n\nfunc TestClientRunMapsResponse(t *testing.T) {\n    // prepare fake response\n    resp := &schemav1.RunResponse{\n        Result: &schemav1.Result{\n            Name:         \"AnalyzerA\",\n            Kind:         \"Pod\",\n            Details:      \"details\",\n            ParentObject: \"Deployment/foo\",\n        },\n    }\n    cli := &Client{analyzerClient: &mockAnalyzerClient{resp: resp}}\n\n    got, err := cli.Run()\n    require.NoError(t, err)\n    require.Equal(t, \"AnalyzerA\", got.Name)\n    require.Equal(t, \"Pod\", got.Kind)\n    require.Equal(t, \"details\", got.Details)\n    require.Equal(t, \"Deployment/foo\", got.ParentObject)\n    require.Len(t, got.Error, 0)\n}"
  },
  {
    "path": "pkg/custom/types.go",
    "content": "package custom\n\ntype Connection struct {\n\tUrl  string `json:\"url\"`\n\tPort string `json:\"port\"`\n}\ntype CustomAnalyzer struct {\n\tName       string     `json:\"name\"`\n\tConnection Connection `json:\"connection\"`\n}\n"
  },
  {
    "path": "pkg/custom_analyzer/customAnalyzer.go",
    "content": "package custom_analyzer\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"regexp\"\n)\n\ntype CustomAnalyzerConfiguration struct {\n\tName       string     `mapstructure:\"name\"`\n\tConnection Connection `mapstructure:\"connection\"`\n}\n\ntype Connection struct {\n\tUrl  string `mapstructure:\"url\"`\n\tPort int    `mapstructure:\"port\"`\n}\n\ntype CustomAnalyzer struct{}\n\nfunc NewCustomAnalyzer() *CustomAnalyzer {\n\treturn &CustomAnalyzer{}\n}\n\nfunc (*CustomAnalyzer) Check(actualConfig []CustomAnalyzerConfiguration, name, url string, port int) error {\n\tvalidNameRegex := `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`\n\tvalidName := regexp.MustCompile(validNameRegex)\n\tif !validName.MatchString(name) {\n\t\treturn fmt.Errorf(\"invalid name format. Must match %s\", validNameRegex)\n\t}\n\n\tfor _, analyzer := range actualConfig {\n\t\tif analyzer.Name == name {\n\t\t\treturn fmt.Errorf(\"custom analyzer with the name '%s' already exists. Please use a different name\", name)\n\t\t}\n\n\t\tif reflect.DeepEqual(analyzer.Connection, Connection{\n\t\t\tUrl:  url,\n\t\t\tPort: port,\n\t\t}) {\n\t\t\treturn fmt.Errorf(\"custom analyzer with the same connection configuration (URL: '%s', Port: %d) already exists. Please use a different URL or port\", url, port)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/integration/aws/aws.go",
    "content": "package aws\n\nimport (\n\t\"os\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/spf13/viper\"\n)\n\ntype AWS struct {\n\tsess *session.Session\n}\n\nfunc (a *AWS) Deploy(namespace string) error {\n\n\treturn nil\n}\n\nfunc (a *AWS) UnDeploy(namespace string) error {\n\ta.sess = nil\n\treturn nil\n}\n\nfunc (a *AWS) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {\n\t// Retrieve AWS credentials from the environment\n\taccessKeyID := os.Getenv(\"AWS_ACCESS_KEY_ID\")\n\tsecretAccessKey := os.Getenv(\"AWS_SECRET_ACCESS_KEY\")\n\tawsProfile := os.Getenv(\"AWS_PROFILE\")\n\n\tvar sess *session.Session\n\tif accessKeyID != \"\" && secretAccessKey != \"\" {\n\t\t// Use access keys if both are provided\n\t\tsess = session.Must(session.NewSessionWithOptions(session.Options{\n\t\t\tConfig: aws.Config{},\n\t\t}))\n\t} else {\n\t\t// Use AWS profile, default to \"default\" if not set\n\t\tif awsProfile == \"\" {\n\t\t\tawsProfile = \"default\"\n\t\t}\n\t\tsess = session.Must(session.NewSessionWithOptions(session.Options{\n\t\t\tProfile:           awsProfile,\n\t\t\tSharedConfigState: session.SharedConfigEnable,\n\t\t}))\n\t}\n\n\ta.sess = sess\n\t(*mergedMap)[\"EKS\"] = &EKSAnalyzer{\n\t\tsession: a.sess,\n\t}\n}\n\nfunc (a *AWS) GetAnalyzerName() []string {\n\n\treturn []string{\"EKS\"}\n}\n\nfunc (a *AWS) GetNamespace() (string, error) {\n\n\treturn \"\", nil\n}\n\nfunc (a *AWS) OwnsAnalyzer(s string) bool {\n\tfor _, az := range a.GetAnalyzerName() {\n\t\tif s == az {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (a *AWS) isFilterActive() bool {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\tfor _, filter := range a.GetAnalyzerName() {\n\t\tfor _, af := range activeFilters {\n\t\t\tif af == filter {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (a *AWS) IsActivate() bool {\n\tif a.isFilterActive() {\n\t\treturn true\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc NewAWS() *AWS {\n\treturn &AWS{}\n}\n"
  },
  {
    "path": "pkg/integration/aws/eks.go",
    "content": "package aws\n\nimport (\n\t\"errors\"\n\t\"github.com/spf13/viper\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/eks\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\ntype EKSAnalyzer struct {\n\tsession *session.Session\n}\n\nfunc (e *EKSAnalyzer) Analyze(analysis common.Analyzer) ([]common.Result, error) {\n\tvar cr []common.Result = []common.Result{}\n\t_ = map[string]common.PreAnalysis{}\n\tsvc := eks.New(e.session)\n\t// Get the name of the current cluster\n\tvar kubeconfig string\n\tkubeconfigFromPath := viper.GetString(\"kubeconfig\")\n\tif kubeconfigFromPath != \"\" {\n\t\tkubeconfig = kubeconfigFromPath\n\t} else {\n\t\tkubeconfig = filepath.Join(os.Getenv(\"HOME\"), \".kube\", \"config\")\n\t}\n\tconfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(\n\t\t&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},\n\t\t&clientcmd.ConfigOverrides{\n\t\t\tCurrentContext: \"\",\n\t\t}).RawConfig()\n\tif err != nil {\n\t\treturn cr, err\n\t}\n\tcurrentConfig := config.CurrentContext\n\n\tif !strings.Contains(currentConfig, \"eks\") {\n\t\treturn cr, errors.New(\"EKS cluster was not detected\")\n\t}\n\n\tinput := &eks.ListClustersInput{}\n\tresult, err := svc.ListClusters(input)\n\tif err != nil {\n\t\treturn cr, err\n\t}\n\tfor _, cluster := range result.Clusters {\n\t\t// describe the cluster\n\t\tif !strings.Contains(currentConfig, *cluster) {\n\t\t\tcontinue\n\t\t}\n\t\tinput := &eks.DescribeClusterInput{\n\t\t\tName: cluster,\n\t\t}\n\t\tresult, err := svc.DescribeCluster(input)\n\t\tif err != nil {\n\t\t\treturn cr, err\n\t\t}\n\t\tif len(result.Cluster.Health.Issues) > 0 {\n\t\t\tfor _, issue := range result.Cluster.Health.Issues {\n\t\t\t\terr := make([]common.Failure, 0)\n\t\t\t\terr = append(err, common.Failure{\n\t\t\t\t\tText:          issue.String(),\n\t\t\t\t\tKubernetesDoc: \"\",\n\t\t\t\t\tSensitive:     nil,\n\t\t\t\t})\n\t\t\t\tcr = append(cr, common.Result{\n\t\t\t\t\tKind:  \"EKS\",\n\t\t\t\t\tName:  \"AWS/EKS\",\n\t\t\t\t\tError: err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\treturn cr, nil\n}\n"
  },
  {
    "path": "pkg/integration/integration.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 integration\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration/aws\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration/kyverno\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration/keda\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration/prometheus\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\t\"github.com/spf13/viper\"\n)\n\ntype IIntegration interface {\n\t// Add adds an integration to the cluster\n\tDeploy(namespace string) error\n\t// Remove removes an integration from the cluster\n\tUnDeploy(namespace string) error\n\t//\n\tAddAnalyzer(*map[string]common.IAnalyzer)\n\n\tGetAnalyzerName() []string\n\t// An integration must keep record of its deployed namespace (if not using --no-install)\n\tGetNamespace() (string, error)\n\n\tOwnsAnalyzer(string) bool\n\n\tIsActivate() bool\n}\n\ntype Integration struct {\n}\n\nvar integrations = map[string]IIntegration{\n\t\"prometheus\": prometheus.NewPrometheus(),\n\t\"aws\":        aws.NewAWS(),\n\t\"keda\":       keda.NewKeda(),\n\t\"kyverno\":    kyverno.NewKyverno(),\n}\n\nfunc NewIntegration() *Integration {\n\treturn &Integration{}\n}\n\nfunc (*Integration) List() []string {\n\tkeys := make([]string, 0, len(integrations))\n\tfor k := range integrations {\n\t\tkeys = append(keys, k)\n\t}\n\treturn keys\n}\n\nfunc (*Integration) Get(name string) (IIntegration, error) {\n\tif _, ok := integrations[name]; !ok {\n\t\treturn nil, errors.New(\"integration not found\")\n\t}\n\treturn integrations[name], nil\n}\n\nfunc (i *Integration) AnalyzerByIntegration(input string) (string, error) {\n\n\tfor _, name := range i.List() {\n\t\tif integ, err := i.Get(name); err == nil {\n\t\t\tif integ.OwnsAnalyzer(input) {\n\t\t\t\treturn name, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", errors.New(\"analyzerbyintegration: no matches found\")\n}\n\nfunc (*Integration) Activate(name string, namespace string, activeFilters []string, skipInstall bool) error {\n\tif _, ok := integrations[name]; !ok {\n\t\treturn errors.New(\"integration not found\")\n\t}\n\n\tif !skipInstall {\n\t\tif err := integrations[name].Deploy(namespace); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to deploy %s integration: %w\", name, err)\n\t\t}\n\t}\n\tmergedFilters := activeFilters\n\tmergedFilters = append(mergedFilters, integrations[name].GetAnalyzerName()...)\n\tuniqueFilters, _ := util.RemoveDuplicates(mergedFilters)\n\n\tviper.Set(\"active_filters\", uniqueFilters)\n\n\tif err := viper.WriteConfig(); err != nil {\n\t\treturn fmt.Errorf(\"error writing config file: %s\", err.Error())\n\n\t}\n\n\treturn nil\n}\n\nfunc (*Integration) Deactivate(name string, namespace string) error {\n\tif _, ok := integrations[name]; !ok {\n\t\treturn errors.New(\"integration not found\")\n\t}\n\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\t// Update filters and remove the specific filters for the integration\n\tfor _, filter := range integrations[name].GetAnalyzerName() {\n\t\tfor x, af := range activeFilters {\n\t\t\tif af == filter {\n\t\t\t\tactiveFilters = append(activeFilters[:x], activeFilters[x+1:]...)\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif err := integrations[name].UnDeploy(namespace); err != nil {\n\t\treturn fmt.Errorf(\"failed to undeploy %s integration: %w\", name, err)\n\t}\n\n\tviper.Set(\"active_filters\", activeFilters)\n\n\tif err := viper.WriteConfig(); err != nil {\n\t\treturn fmt.Errorf(\"error writing config file: %s\", err.Error())\n\n\t}\n\n\treturn nil\n}\n\nfunc (*Integration) IsActivate(name string) (bool, error) {\n\tif _, ok := integrations[name]; !ok {\n\t\treturn false, errors.New(\"integration not found\")\n\t}\n\treturn integrations[name].IsActivate(), nil\n}\n"
  },
  {
    "path": "pkg/integration/integration_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 integration\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAnalyzerByIntegration(t *testing.T) {\n\tintegration := NewIntegration()\n\t_, err := integration.Get(\"invalid-name\")\n\trequire.ErrorContains(t, err, \"integration not found\")\n\n\ttests := []struct {\n\t\tname         string\n\t\texpectedName string\n\t\texpectedErr  string\n\t}{\n\t\t{\n\t\t\tname:        \"random\",\n\t\t\texpectedErr: \"analyzerbyintegration: no matches found\",\n\t\t},\n\t\t{\n\t\t\tname:         \"PrometheusConfigValidate\",\n\t\t\texpectedName: \"prometheus\",\n\t\t},\n\t\t{\n\t\t\tname:         \"PrometheusConfigRelabelReport\",\n\t\t\texpectedName: \"prometheus\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tname, err := integration.AnalyzerByIntegration(tt.name)\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, tt.expectedName, name)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t\trequire.Empty(t, name)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestActivate(t *testing.T) {\n\tintegration := NewIntegration()\n\terr := integration.Activate(\"prometheus\", \"\", []string{}, true)\n\trequire.ErrorContains(t, err, \"error writing config file:\")\n\n\terr = integration.Deactivate(\"prometheus\", \"\")\n\trequire.ErrorContains(t, err, \"error writing config file:\")\n\n\tconfigFileName := \"config.json\"\n\t_, err = os.CreateTemp(\"\", configFileName)\n\trequire.NoError(t, err)\n\tdefer os.Remove(configFileName)\n\n\t// Set the configuration file in viper\n\tviper.SetConfigType(\"json\")\n\tviper.SetConfigFile(configFileName)\n\n\tinteNotFoundErr := \"integration not found\"\n\ttests := []struct {\n\t\tname                    string\n\t\tnamespace               string\n\t\tactiveFilters           []string\n\t\tskipInstall             bool\n\t\texpectedIsActivate      bool\n\t\texpectedActivationErr   string\n\t\texpectedIsActivateError string\n\t\texpectedDeactivationErr string\n\t}{\n\t\t{\n\t\t\tname:                    \"invalid integration\",\n\t\t\texpectedActivationErr:   inteNotFoundErr,\n\t\t\texpectedIsActivateError: inteNotFoundErr,\n\t\t\texpectedDeactivationErr: inteNotFoundErr,\n\t\t},\n\t\t{\n\t\t\tname:               \"prometheus\",\n\t\t\tskipInstall:        true,\n\t\t\texpectedIsActivate: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := integration.Activate(tt.name, tt.namespace, tt.activeFilters, tt.skipInstall)\n\t\t\tif tt.expectedActivationErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedActivationErr)\n\t\t\t}\n\n\t\t\tok, err := integration.IsActivate(tt.name)\n\t\t\tif tt.expectedIsActivateError == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, tt.expectedIsActivate, ok)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedIsActivateError)\n\t\t\t}\n\n\t\t\terr = integration.Deactivate(tt.name, tt.namespace)\n\t\t\tif tt.expectedDeactivationErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedDeactivationErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/integration/keda/keda.go",
    "content": "package keda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/typed/keda/v1alpha1\"\n\thelmclient \"github.com/mittwald/go-helm-client\"\n\t\"github.com/spf13/viper\"\n\t\"helm.sh/helm/v3/pkg/repo\"\n)\n\nvar (\n\tRepo          = getEnv(\"KEDA_REPO\", \"https://kedacore.github.io/charts\")\n\tVersion       = getEnv(\"KEDA_VERSION\", \"2.11.2\")\n\tChartName     = getEnv(\"KEDA_CHART_NAME\", \"keda\")\n\tRepoShortName = getEnv(\"KEDA_REPO_SHORT_NAME\", \"keda\")\n\tReleaseName   = getEnv(\"KEDA_RELEASE_NAME\", \"keda-k8sgpt\")\n)\n\ntype Keda struct {\n\thelm helmclient.Client\n}\n\nfunc getEnv(key, defaultValue string) string {\n\tvalue := os.Getenv(key)\n\tif value == \"\" {\n\t\treturn defaultValue\n\t}\n\treturn value\n}\n\nfunc NewKeda() *Keda {\n\thelmClient, err := helmclient.New(&helmclient.Options{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn &Keda{\n\t\thelm: helmClient,\n\t}\n}\n\nfunc (k *Keda) Deploy(namespace string) error {\n\t// Add the repository\n\tchartRepo := repo.Entry{\n\t\tName: RepoShortName,\n\t\tURL:  Repo,\n\t}\n\t// Add a chart-repository to the client.\n\tif err := k.helm.AddOrUpdateChartRepo(chartRepo); err != nil {\n\t\tpanic(err)\n\t}\n\n\tchartSpec := helmclient.ChartSpec{\n\t\tReleaseName: ReleaseName,\n\t\tChartName:   fmt.Sprintf(\"%s/%s\", RepoShortName, ChartName),\n\t\tNamespace:   namespace,\n\n\t\t//TODO: All of this should be configurable\n\t\tUpgradeCRDs:     true,\n\t\tWait:            false,\n\t\tTimeout:         300,\n\t\tCreateNamespace: true,\n\t}\n\n\t// Install a chart release.\n\t// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.\n\tif _, err := k.helm.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (k *Keda) UnDeploy(namespace string) error {\n\tkubecontext := viper.GetString(\"kubecontext\")\n\tkubeconfig := viper.GetString(\"kubeconfig\")\n\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\tif err != nil {\n\t\t// TODO: better error handling\n\t\tcolor.Red(\"Error initialising kubernetes client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tkedaNamespace, _ := k.GetNamespace()\n\tcolor.Blue(fmt.Sprintf(\"Keda namespace: %s\\n\", kedaNamespace))\n\n\tkClient, _ := v1alpha1.NewForConfig(client.Config)\n\n\tscaledObjectList, _ := kClient.ScaledObjects(\"\").List(context.Background(), v1.ListOptions{})\n\tscaledJobList, _ := kClient.ScaledJobs(\"\").List(context.Background(), v1.ListOptions{})\n\ttriggerAuthenticationList, _ := kClient.TriggerAuthentications(\"\").List(context.Background(), v1.ListOptions{})\n\tclusterTriggerAuthenticationsList, _ := kClient.ClusterTriggerAuthentications().List(context.Background(), v1.ListOptions{})\n\n\t// Before uninstalling the Helm chart, we need to delete Keda resources\n\tfor _, scaledObject := range scaledObjectList.Items {\n\t\terr := kClient.ScaledObjects(scaledObject.Namespace).Delete(context.Background(), scaledObject.Name, v1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error deleting scaledObject %s: %v\\n\", scaledObject.Name, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"Deleted scaledObject %s in namespace %s\\n\", scaledObject.Name, scaledObject.Namespace)\n\t\t}\n\t}\n\n\tfor _, scaledJob := range scaledJobList.Items {\n\t\terr := kClient.ScaledJobs(scaledJob.Namespace).Delete(context.Background(), scaledJob.Name, v1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error deleting scaledJob %s: %v\\n\", scaledJob.Name, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"Deleted scaledJob %s in namespace %s\\n\", scaledJob.Name, scaledJob.Namespace)\n\t\t}\n\t}\n\n\tfor _, triggerAuthentication := range triggerAuthenticationList.Items {\n\t\terr := kClient.TriggerAuthentications(triggerAuthentication.Namespace).Delete(context.Background(), triggerAuthentication.Name, v1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error deleting triggerAuthentication %s: %v\\n\", triggerAuthentication.Name, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"Deleted triggerAuthentication %s in namespace %s\\n\", triggerAuthentication.Name, triggerAuthentication.Namespace)\n\t\t}\n\t}\n\n\tfor _, clusterTriggerAuthentication := range clusterTriggerAuthenticationsList.Items {\n\t\terr := kClient.ClusterTriggerAuthentications().Delete(context.Background(), clusterTriggerAuthentication.Name, v1.DeleteOptions{})\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Error deleting clusterTriggerAuthentication %s: %v\\n\", clusterTriggerAuthentication.Name, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"Deleted clusterTriggerAuthentication %s\\n\", clusterTriggerAuthentication.Name)\n\t\t}\n\t}\n\n\tchartSpec := helmclient.ChartSpec{\n\t\tReleaseName: ReleaseName,\n\t\tChartName:   fmt.Sprintf(\"%s/%s\", RepoShortName, ChartName),\n\t\tNamespace:   namespace,\n\t\tUpgradeCRDs: true,\n\t\tWait:        false,\n\t\tTimeout:     300,\n\t}\n\t// Uninstall the chart release.\n\t// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.\n\tif err := k.helm.UninstallRelease(&chartSpec); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (k *Keda) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {\n\t(*mergedMap)[\"ScaledObject\"] = &ScaledObjectAnalyzer{}\n}\n\nfunc (k *Keda) GetAnalyzerName() []string {\n\treturn []string{\n\t\t\"ScaledObject\",\n\t}\n}\n\nfunc (k *Keda) GetNamespace() (string, error) {\n\treleases, err := k.helm.ListDeployedReleases()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfor _, rel := range releases {\n\t\tif rel.Name == ReleaseName {\n\t\t\treturn rel.Namespace, nil\n\t\t}\n\t}\n\treturn \"\", status.Error(codes.NotFound, \"keda release not found\")\n}\n\nfunc (k *Keda) OwnsAnalyzer(analyzer string) bool {\n\tfor _, a := range k.GetAnalyzerName() {\n\t\tif analyzer == a {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (k *Keda) isFilterActive() bool {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\tfor _, filter := range k.GetAnalyzerName() {\n\t\tfor _, af := range activeFilters {\n\t\t\tif af == filter {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (k *Keda) isDeployed() bool {\n\tkubecontext := viper.GetString(\"kubecontext\")\n\tkubeconfig := viper.GetString(\"kubeconfig\")\n\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\tif err != nil {\n\t\t// TODO: better error handling\n\t\tcolor.Red(\"Error initialising kubernetes client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tgroups, _, err := client.Client.Discovery().ServerGroupsAndResources()\n\tif err != nil {\n\t\t// TODO: better error handling\n\t\tcolor.Red(\"Error initialising discovery client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, group := range groups {\n\t\tif group.Name == \"keda.sh\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (k *Keda) IsActivate() bool {\n\treturn k.isFilterActive() && k.isDeployed()\n}\n"
  },
  {
    "path": "pkg/integration/keda/scaledobject_analyzer.go",
    "content": "package keda\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tkedaSchema \"github.com/kedacore/keda/v2/apis/keda/v1alpha1\"\n\t\"github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/typed/keda/v1alpha1\"\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\ntype ScaledObjectAnalyzer struct{}\n\nfunc (s *ScaledObjectAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tkClient, _ := v1alpha1.NewForConfig(a.Client.GetConfig())\n\tkind := \"ScaledObject\"\n\n\tapiDoc := kubernetes.K8sApiReference{\n\t\tKind:          kind,\n\t\tApiVersion:    kedaSchema.GroupVersion,\n\t\tOpenapiSchema: a.OpenapiSchema,\n\t}\n\n\tlist, err := kClient.ScaledObjects(a.Namespace).List(a.Context, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, so := range list.Items {\n\t\tvar failures []common.Failure\n\n\t\tscaleTargetRef := so.Spec.ScaleTargetRef\n\t\tif scaleTargetRef.Kind == \"\" {\n\t\t\tscaleTargetRef.Kind = \"Deployment\"\n\t\t}\n\n\t\tvar podInfo PodInfo\n\n\t\tswitch scaleTargetRef.Kind {\n\t\tcase \"Deployment\":\n\t\t\tdeployment, err := a.Client.GetClient().AppsV1().Deployments(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = DeploymentInfo{deployment}\n\t\t\t}\n\t\tcase \"ReplicationController\":\n\t\t\trc, err := a.Client.GetClient().CoreV1().ReplicationControllers(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = ReplicationControllerInfo{rc}\n\t\t\t}\n\t\tcase \"ReplicaSet\":\n\t\t\trs, err := a.Client.GetClient().AppsV1().ReplicaSets(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = ReplicaSetInfo{rs}\n\t\t\t}\n\t\tcase \"StatefulSet\":\n\t\t\tss, err := a.Client.GetClient().AppsV1().StatefulSets(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})\n\t\t\tif err == nil {\n\t\t\t\tpodInfo = StatefulSetInfo{ss}\n\t\t\t}\n\t\tdefault:\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:      fmt.Sprintf(\"ScaledObject uses %s as ScaleTargetRef which is not an option.\", scaleTargetRef.Kind),\n\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t})\n\t\t}\n\n\t\tif podInfo == nil {\n\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.scaleTargetRef\")\n\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText:          fmt.Sprintf(\"ScaledObject uses %s/%s as ScaleTargetRef which does not exist.\", scaleTargetRef.Kind, scaleTargetRef.Name),\n\t\t\t\tKubernetesDoc: doc,\n\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t{\n\t\t\t\t\t\tUnmasked: scaleTargetRef.Name,\n\t\t\t\t\t\tMasked:   util.MaskString(scaleTargetRef.Name),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t} else {\n\t\t\tcontainers := len(podInfo.GetPodSpec().Containers)\n\t\t\tfor _, container := range podInfo.GetPodSpec().Containers {\n\t\t\t\tfor _, trigger := range so.Spec.Triggers {\n\t\t\t\t\tif trigger.Type == \"cpu\" || trigger.Type == \"memory\" {\n\t\t\t\t\t\tif container.Resources.Requests == nil || container.Resources.Limits == nil {\n\t\t\t\t\t\t\tcontainers--\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif containers <= 0 {\n\t\t\t\tdoc := apiDoc.GetApiDocV2(\"spec.scaleTargetRef.kind\")\n\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:          fmt.Sprintf(\"%s %s/%s does not have resource configured.\", scaleTargetRef.Kind, so.Namespace, scaleTargetRef.Name),\n\t\t\t\t\tKubernetesDoc: doc,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: scaleTargetRef.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(scaleTargetRef.Name),\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\tevt, err := util.FetchLatestEvent(a.Context, a.Client, so.Namespace, so.Name)\n\t\t\tif err != nil || evt == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif evt.Type != \"Normal\" {\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText: evt.Message,\n\t\t\t\t\tSensitive: []common.Sensitive{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tUnmasked: scaleTargetRef.Name,\n\t\t\t\t\t\t\tMasked:   util.MaskString(scaleTargetRef.Name),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", so.Namespace, so.Name)] = common.PreAnalysis{\n\t\t\t\tScaledObject:   so,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, _ := util.GetParent(a.Client, value.ScaledObject.ObjectMeta)\n\t\tcurrentAnalysis.ParentObject = parent\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\ntype PodInfo interface {\n\tGetPodSpec() corev1.PodSpec\n}\n\ntype DeploymentInfo struct {\n\t*appsv1.Deployment\n}\n\nfunc (d DeploymentInfo) GetPodSpec() corev1.PodSpec {\n\treturn d.Spec.Template.Spec\n}\n\n// define a structure for ReplicationController\ntype ReplicationControllerInfo struct {\n\t*corev1.ReplicationController\n}\n\nfunc (rc ReplicationControllerInfo) GetPodSpec() corev1.PodSpec {\n\treturn rc.Spec.Template.Spec\n}\n\n// define a structure for ReplicaSet\ntype ReplicaSetInfo struct {\n\t*appsv1.ReplicaSet\n}\n\nfunc (rs ReplicaSetInfo) GetPodSpec() corev1.PodSpec {\n\treturn rs.Spec.Template.Spec\n}\n\n// define a structure for StatefulSet\ntype StatefulSetInfo struct {\n\t*appsv1.StatefulSet\n}\n\n// implement PodInfo for StatefulSetInfo\nfunc (ss StatefulSetInfo) GetPodSpec() corev1.PodSpec {\n\treturn ss.Spec.Template.Spec\n}\n"
  },
  {
    "path": "pkg/integration/kyverno/analyzer.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 kyverno\n\nimport (\n\t\"fmt\"\n\n\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\n\t\"github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2\"\n)\n\n//\t\"github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2\"\n\ntype KyvernoAnalyzer struct {\n\tpolicyReportAnalysis  bool\n\tclusterReportAnalysis bool\n}\n\nfunc (KyvernoAnalyzer) analyzePolicyReports(a common.Analyzer) ([]common.Result, error) {\n\tresult := &v1alpha2.PolicyReportList{}\n\tclient := a.Client.CtrlClient\n\n\terr := v1alpha2.AddToScheme(client.Scheme())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := client.List(a.Context, result, &ctrl.ListOptions{Namespace: a.Namespace}); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Find criticals and get CVE\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, report := range result.Items {\n\n\t\t// For each pod there may be multiple vulnerabilities\n\t\tvar failures []common.Failure\n\t\tfor _, vuln := range report.Results {\n\t\t\tif vuln.Result == \"fail\" {\n\t\t\t\t// get the vulnerability ID\n\t\t\t\t// get the vulnerability description\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"policy failure: %s (message: %s)\", vuln.Policy, vuln.Message),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", report.Namespace,\n\t\t\t\treport.Name)] = common.PreAnalysis{\n\t\t\t\tKyvernoPolicyReport: report,\n\t\t\t\tFailureDetails:      failures,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  \"PolicyReport\",\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, _ := util.GetParent(a.Client, value.KyvernoPolicyReport.ObjectMeta)\n\t\tcurrentAnalysis.ParentObject = parent\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n\n}\n\nfunc (t KyvernoAnalyzer) analyzeClusterPolicyReports(a common.Analyzer) ([]common.Result, error) {\n\tresult := &v1alpha2.ClusterPolicyReportList{}\n\tclient := a.Client.CtrlClient\n\n\terr := v1alpha2.AddToScheme(client.Scheme())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Find criticals and get CVE\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\n\tfor _, report := range result.Items {\n\n\t\t// For each pod there may be multiple vulnerabilities\n\t\tvar failures []common.Failure\n\t\tfor _, vuln := range report.Results {\n\t\t\tif vuln.Severity == \"CRITICAL\" {\n\t\t\t\t// get the vulnerability ID\n\t\t\t\t// get the vulnerability description\n\t\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\t\tText:      fmt.Sprintf(\"critical Vulnerability found ID: %s (learn more at: %s)\", vuln.ID, vuln.Source),\n\t\t\t\t\tSensitive: []common.Sensitive{},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", report.Namespace,\n\t\t\t\treport.Name)] = common.PreAnalysis{\n\t\t\t\tKyvernoClusterPolicyReport: report,\n\t\t\t\tFailureDetails:             failures,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  \"ClusterPolicyReport\",\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\n\t\tparent, _ := util.GetParent(a.Client, value.KyvernoClusterPolicyReport.ObjectMeta)\n\t\tcurrentAnalysis.ParentObject = parent\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\nfunc (t KyvernoAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\n\tif t.policyReportAnalysis {\n\t\tcommon := make([]common.Result, 0)\n\t\tvresult, err := t.analyzePolicyReports(a)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcommon = append(common, vresult...)\n\t\treturn common, nil\n\t}\n\tif t.clusterReportAnalysis {\n\t\tcommon := make([]common.Result, 0)\n\t\tcresult, err := t.analyzeClusterPolicyReports(a)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcommon = append(common, cresult...)\n\t\treturn common, nil\n\t}\n\treturn make([]common.Result, 0), nil\n}\n"
  },
  {
    "path": "pkg/integration/kyverno/analyzer_test.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 kyverno\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client/fake\"\n)\n\nfunc buildFakeClient(t *testing.T) client.Client {\n\tobjects := []client.Object{\n\t\t&v1alpha2.PolicyReport{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"policy-1\",\n\t\t\t\tNamespace: \"test-ns\",\n\t\t\t},\n\t\t\tResults: []v1alpha2.PolicyReportResult{\n\t\t\t\t{\n\t\t\t\t\tCategory: \"Other\",\n\t\t\t\t\tMessage:  \"validation failure: Images built more than 6 months ago are prohibited.\",\n\t\t\t\t\tPolicy:   \"block-stale-images\",\n\t\t\t\t\tResult:   \"fail\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&v1alpha2.PolicyReport{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"policy-2\",\n\t\t\t\tNamespace: \"other-ns\",\n\t\t\t},\n\t\t\tResults: []v1alpha2.PolicyReportResult{\n\t\t\t\t{\n\t\t\t\t\tCategory: \"Other\",\n\t\t\t\t\tMessage:  \"validation failure: Images built more than 6 months ago are prohibited.\",\n\t\t\t\t\tPolicy:   \"block-stale-images\",\n\t\t\t\t\tResult:   \"fail\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tscheme := runtime.NewScheme()\n\terr := v1alpha2.AddToScheme(scheme)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\treturn fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()\n}\n\nfunc TestAnalyzerNamespaceFiltering(t *testing.T) {\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: buildFakeClient(t),\n\t\t},\n\t\tContext:   context.Background(),\n\t\tNamespace: \"test-ns\",\n\t}\n\n\t// Create and run analyzer\n\tanalyzer := KyvernoAnalyzer{\n\t\tpolicyReportAnalysis: true,\n\t}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// Verify results\n\tassert.Equal(t, len(results), 1)\n\tassert.Equal(t, results[0].Kind, \"PolicyReport\")\n\tassert.Equal(t, results[0].Name, \"test-ns/policy-1\")\n}\n\nfunc TestAnalyzerAllNamespace(t *testing.T) {\n\n\tconfig := common.Analyzer{\n\t\tClient: &kubernetes.Client{\n\t\t\tCtrlClient: buildFakeClient(t),\n\t\t},\n\t\tContext: context.Background(),\n\t}\n\n\t// Create and run analyzer\n\tanalyzer := KyvernoAnalyzer{\n\t\tpolicyReportAnalysis: true,\n\t}\n\tresults, err := analyzer.Analyze(config)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\t// Verify results\n\tassert.Equal(t, len(results), 2)\n\n}\n"
  },
  {
    "path": "pkg/integration/kyverno/kyverno.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 kyverno\n\nimport (\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/spf13/viper\"\n)\n\ntype Kyverno struct{}\n\nfunc NewKyverno() *Kyverno {\n\treturn &Kyverno{}\n}\n\nfunc (k *Kyverno) GetAnalyzerName() []string {\n\treturn []string{\n\t\t//from wgpolicyk8s.io/v1alpha2\n\t\t\"PolicyReport\",\n\t\t\"ClusterPolicyReport\",\n\t}\n}\n\nfunc (k *Kyverno) OwnsAnalyzer(analyzer string) bool {\n\n\tfor _, a := range k.GetAnalyzerName() {\n\t\tif analyzer == a {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (k *Kyverno) isDeployed() bool {\n\t// check if wgpolicyk8s apigroup is available as a marker if new policy resource available is installed on the cluster\n\tkubecontext := viper.GetString(\"kubecontext\")\n\tkubeconfig := viper.GetString(\"kubeconfig\")\n\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\tif err != nil {\n\t\t// TODO: better error handling\n\t\tcolor.Red(\"Error initialising kubernetes client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tgroups, _, err := client.Client.Discovery().ServerGroupsAndResources()\n\tif err != nil {\n\t\t// TODO: better error handling\n\t\tcolor.Red(\"Error initialising discovery client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\tfor _, group := range groups {\n\t\tif group.Name == \"kyverno.io\" {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (k *Kyverno) isFilterActive() bool {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\tfor _, filter := range k.GetAnalyzerName() {\n\t\tfor _, af := range activeFilters {\n\t\t\tif af == filter {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (k *Kyverno) IsActivate() bool {\n\tif k.isFilterActive() && k.isDeployed() {\n\t\treturn true\n\t} else {\n\t\treturn false\n\t}\n}\n\nfunc (k *Kyverno) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {\n\n\t(*mergedMap)[\"PolicyReport\"] = &KyvernoAnalyzer{\n\t\tpolicyReportAnalysis: true,\n\t}\n\t(*mergedMap)[\"ClusterPolicyReport\"] = &KyvernoAnalyzer{\n\t\tclusterReportAnalysis: true,\n\t}\n}\n\nfunc (k *Kyverno) Deploy(namespace string) error {\n\treturn nil\n}\n\nfunc (k *Kyverno) UnDeploy(_ string) error {\n\treturn nil\n}\n\nfunc (t *Kyverno) GetNamespace() (string, error) {\n\treturn \"\", nil\n}\n"
  },
  {
    "path": "pkg/integration/prometheus/config_analyzer.go",
    "content": "package prometheus\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tpromconfig \"github.com/prometheus/prometheus/config\"\n\tyaml \"gopkg.in/yaml.v2\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\nconst (\n\tprometheusContainerName     = \"prometheus\"\n\tconfigReloaderContainerName = \"config-reloader\"\n\tprometheusConfigFlag        = \"--config.file=\"\n\tconfigReloaderConfigFlag    = \"--config-file=\"\n)\n\nvar prometheusPodLabels = map[string]string{\n\t\"app\":                    \"prometheus\",\n\t\"app.kubernetes.io/name\": \"prometheus\",\n}\n\ntype ConfigAnalyzer struct {\n}\n\n// podConfig groups a specific pod with the Prometheus configuration and any\n// other state used for informing the common.Result.\ntype podConfig struct {\n\tb   []byte\n\tpod *corev1.Pod\n}\n\nfunc (c *ConfigAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tctx := a.Context\n\tclient := a.Client.GetClient()\n\tnamespace := a.Namespace\n\tkind := ConfigValidate\n\n\tpodConfigs, err := findPrometheusPodConfigs(ctx, client, namespace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\tfor _, pc := range podConfigs {\n\t\tvar failures []common.Failure\n\t\tpod := pc.pod\n\n\t\t// Check upstream validation.\n\t\t// The Prometheus configuration structs do not generally have validation\n\t\t// methods and embed their validation logic in the UnmarshalYAML methods.\n\t\tconfig, err := unmarshalPromConfigBytes(pc.b)\n\t\tif err != nil {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\"error validating Prometheus YAML configuration: %s\", err),\n\t\t\t})\n\t\t}\n\t\t_, err = yaml.Marshal(config)\n\t\tif err != nil {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\"error validating Prometheus struct configuration: %s\", err),\n\t\t\t})\n\t\t}\n\n\t\t// Check for empty scrape config.\n\t\tif len(config.ScrapeConfigs) == 0 {\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: \"no scrape configurations. Prometheus will not scrape any metrics.\",\n\t\t\t})\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", pod.Namespace, pod.Name)] = common.PreAnalysis{\n\t\t\t\tPod:            *pod,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\tparent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)\n\t\tcurrentAnalysis.ParentObject = parent\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n\nfunc configKey(namespace string, volume *corev1.Volume) (string, error) {\n\tif volume.ConfigMap != nil {\n\t\treturn fmt.Sprintf(\"configmap/%s/%s\", namespace, volume.ConfigMap.Name), nil\n\t} else if volume.Secret != nil {\n\t\treturn fmt.Sprintf(\"secret/%s/%s\", namespace, volume.Secret.SecretName), nil\n\t} else {\n\t\treturn \"\", errors.New(\"volume format must be ConfigMap or Secret\")\n\t}\n}\n\nfunc findPrometheusPodConfigs(ctx context.Context, client kubernetes.Interface, namespace string) ([]podConfig, error) {\n\tvar configs []podConfig\n\tpods, err := findPrometheusPods(ctx, client, namespace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar configCache = make(map[string]bool)\n\n\tfor _, pod := range pods {\n\t\t// Extract volume of Prometheus config.\n\t\tvolume, key, err := findPrometheusConfigVolumeAndKey(ctx, client, &pod)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// See if we processed it already; if so, don't process again.\n\t\tck, err := configKey(pod.Namespace, volume)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t_, ok := configCache[ck]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\t\tconfigCache[ck] = true\n\n\t\t// Extract Prometheus config bytes from volume.\n\t\tb, err := extractPrometheusConfigFromVolume(ctx, client, volume, pod.Namespace, key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconfigs = append(configs, podConfig{\n\t\t\tpod: &pod,\n\t\t\tb:   b,\n\t\t})\n\t}\n\n\treturn configs, nil\n}\n\nfunc findPrometheusPods(ctx context.Context, client kubernetes.Interface, namespace string) ([]corev1.Pod, error) {\n\tvar proms []corev1.Pod\n\tfor k, v := range prometheusPodLabels {\n\t\tpods, err := util.GetPodListByLabels(client, namespace, map[string]string{\n\t\t\tk: v,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproms = append(proms, pods.Items...)\n\t}\n\n\t// If we still haven't found any Prometheus pods, make a last-ditch effort to\n\t// scrape the namespace for \"prometheus\" containers.\n\tif len(proms) == 0 {\n\t\tpods, err := client.CoreV1().Pods(namespace).List(ctx, v1.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, pod := range pods.Items {\n\t\t\tfor _, c := range pod.Spec.Containers {\n\t\t\t\tif c.Name == prometheusContainerName {\n\t\t\t\t\tproms = append(proms, pod)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn proms, nil\n}\n\nfunc findPrometheusConfigPath(ctx context.Context, client kubernetes.Interface, pod *corev1.Pod) (string, error) {\n\tvar path string\n\tvar err error\n\tfor _, container := range pod.Spec.Containers {\n\t\tfor _, arg := range container.Args {\n\t\t\t// Prefer the config-reloader container config file as it normally\n\t\t\t// references the ConfigMap or Secret volume mount.\n\t\t\t// Fallback to the prometheus container if that's not found.\n\t\t\tif strings.HasPrefix(arg, prometheusConfigFlag) {\n\t\t\t\tpath = strings.TrimPrefix(arg, prometheusConfigFlag)\n\t\t\t}\n\t\t\tif strings.HasPrefix(arg, configReloaderConfigFlag) {\n\t\t\t\tpath = strings.TrimPrefix(arg, configReloaderConfigFlag)\n\t\t\t}\n\t\t}\n\t\tif container.Name == configReloaderContainerName {\n\t\t\treturn path, nil\n\t\t}\n\t}\n\tif path == \"\" {\n\t\terr = fmt.Errorf(\"prometheus config path not found in pod: %s\", pod.Name)\n\t}\n\treturn path, err\n}\n\nfunc findPrometheusConfigVolumeAndKey(ctx context.Context, client kubernetes.Interface, pod *corev1.Pod) (*corev1.Volume, string, error) {\n\tpath, err := findPrometheusConfigPath(ctx, client, pod)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\t// Find the volumeMount the config path is pointing to.\n\tvar volumeName = \"\"\n\tfor _, container := range pod.Spec.Containers {\n\t\tfor _, vm := range container.VolumeMounts {\n\t\t\tif strings.HasPrefix(path, vm.MountPath) {\n\t\t\t\tvolumeName = vm.Name\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Get the actual Volume from the name.\n\tfor _, volume := range pod.Spec.Volumes {\n\t\tif volume.Name == volumeName {\n\t\t\treturn &volume, filepath.Base(path), nil\n\t\t}\n\t}\n\n\treturn nil, \"\", errors.New(\"volume for Prometheus config not found\")\n}\n\nfunc extractPrometheusConfigFromVolume(ctx context.Context, client kubernetes.Interface, volume *corev1.Volume, namespace, key string) ([]byte, error) {\n\tvar b []byte\n\tvar ok bool\n\t// Check for Secret volume.\n\tif vs := volume.Secret; vs != nil {\n\t\ts, err := client.CoreV1().Secrets(namespace).Get(ctx, vs.SecretName, v1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tb, ok = s.Data[key]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unable to find file key in secret: %s\", key)\n\t\t}\n\t}\n\t// Check for ConfigMap volume.\n\tif vcm := volume.ConfigMap; vcm != nil {\n\t\tcm, err := client.CoreV1().ConfigMaps(namespace).Get(ctx, vcm.Name, v1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ts, ok := cm.Data[key]\n\t\tb = []byte(s)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unable to find file key in configmap: %s\", key)\n\t\t}\n\t}\n\treturn b, nil\n}\n\nfunc unmarshalPromConfigBytes(b []byte) (*promconfig.Config, error) {\n\tvar config promconfig.Config\n\t// Unmarshal the data into a Prometheus config.\n\tif err := yaml.Unmarshal(b, &config); err == nil {\n\t\treturn &config, nil\n\t\t// If there were errors, try gunziping the data.\n\t} else if content := http.DetectContentType(b); content == \"application/x-gzip\" {\n\t\tr, err := gzip.NewReader(bytes.NewBuffer(b))\n\t\tif err != nil {\n\t\t\treturn &config, err\n\t\t}\n\t\tgunzipBytes, err := io.ReadAll(r)\n\t\tif err != nil {\n\t\t\treturn &config, err\n\t\t}\n\t\terr = yaml.Unmarshal(gunzipBytes, &config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &config, nil\n\t} else {\n\t\treturn &config, err\n\t}\n}\n"
  },
  {
    "path": "pkg/integration/prometheus/prometheus.go",
    "content": "package prometheus\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/spf13/viper\"\n)\n\nconst (\n\tConfigValidate = \"PrometheusConfigValidate\"\n\tConfigRelabel  = \"PrometheusConfigRelabelReport\"\n)\n\ntype Prometheus struct {\n}\n\nfunc NewPrometheus() *Prometheus {\n\treturn &Prometheus{}\n}\n\nfunc (p *Prometheus) Deploy(namespace string) error {\n\t// no-op\n\tcolor.Green(\"Activating prometheus integration...\")\n\t// TODO(pintohutch): add timeout or inherit an upstream context\n\t// for better signal management.\n\tctx := context.Background()\n\tkubecontext := viper.GetString(\"kubecontext\")\n\tkubeconfig := viper.GetString(\"kubeconfig\")\n\tclient, err := kubernetes.NewClient(kubecontext, kubeconfig)\n\tif err != nil {\n\t\tcolor.Red(\"Error initialising kubernetes client: %v\", err)\n\t\tos.Exit(1)\n\t}\n\n\t// We just care about existing deployments.\n\t// Try and find Prometheus configurations in the cluster using the provided namespace.\n\t//\n\t// Note: We could cache this state and inject it into the various analyzers\n\t// to save additional parsing later.\n\t// However, the state of the cluster can change from activation to analysis,\n\t// so we would want to run this again on each analyze call anyway.\n\t//\n\t// One consequence of this is one can run `activate` in one namespace\n\t// and run `analyze` in another, without issues, as long as Prometheus\n\t// is found in both.\n\t// We accept this as a trade-off for the time-being to avoid having the tool\n\t// manage Prometheus on the behalf of users.\n\tpodConfigs, err := findPrometheusPodConfigs(ctx, client.GetClient(), namespace)\n\tif err != nil {\n\t\tcolor.Red(\"Error discovering Prometheus workloads: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tif len(podConfigs) == 0 {\n\t\tcolor.Yellow(fmt.Sprintf(`Prometheus installation not found in namespace: %s.\n\t\tPlease ensure Prometheus is deployed to analyze.`, namespace))\n\t\treturn errors.New(\"no prometheus installation found\")\n\t}\n\t// Prime state of the analyzer so\n\tcolor.Green(\"Found existing installation\")\n\treturn nil\n}\n\nfunc (p *Prometheus) UnDeploy(_ string) error {\n\t// no-op\n\t// We just care about existing deployments.\n\tcolor.Yellow(\"Integration will leave Prometheus resources deployed. This is an effective no-op in the cluster.\")\n\treturn nil\n}\n\nfunc (p *Prometheus) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {\n\t(*mergedMap)[ConfigValidate] = &ConfigAnalyzer{}\n\t(*mergedMap)[ConfigRelabel] = &RelabelAnalyzer{}\n}\n\nfunc (p *Prometheus) GetAnalyzerName() []string {\n\treturn []string{ConfigValidate, ConfigRelabel}\n}\n\nfunc (p *Prometheus) GetNamespace() (string, error) {\n\treturn \"\", nil\n}\n\nfunc (p *Prometheus) OwnsAnalyzer(analyzer string) bool {\n\treturn (analyzer == ConfigValidate) || (analyzer == ConfigRelabel)\n}\n\nfunc (t *Prometheus) IsActivate() bool {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\tfor _, filter := range t.GetAnalyzerName() {\n\t\tfor _, af := range activeFilters {\n\t\t\tif af == filter {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/integration/prometheus/relabel_analyzer.go",
    "content": "package prometheus\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/common\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/util\"\n\tdiscoverykube \"github.com/prometheus/prometheus/discovery/kubernetes\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype RelabelAnalyzer struct {\n}\n\nfunc (r *RelabelAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {\n\tctx := a.Context\n\tclient := a.Client.GetClient()\n\tnamespace := a.Namespace\n\tkind := ConfigRelabel\n\n\tpodConfigs, err := findPrometheusPodConfigs(ctx, client, namespace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar preAnalysis = map[string]common.PreAnalysis{}\n\tfor _, pc := range podConfigs {\n\t\tvar failures []common.Failure\n\t\tpod := pc.pod\n\n\t\t// Check upstream validation.\n\t\t// The Prometheus configuration structs do not generally have validation\n\t\t// methods and embed their validation logic in the UnmarshalYAML methods.\n\t\tconfig, _ := unmarshalPromConfigBytes(pc.b)\n\t\t// Limit output for brevity.\n\t\tlimit := 6\n\t\ti := 0\n\t\tfor _, sc := range config.ScrapeConfigs {\n\t\t\tif i == limit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif sc == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbrc, _ := yaml.Marshal(sc.RelabelConfigs)\n\t\t\tvar bsd []byte\n\t\t\tfor _, cfg := range sc.ServiceDiscoveryConfigs {\n\t\t\t\tks, ok := cfg.(*discoverykube.SDConfig)\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbsd, _ = yaml.Marshal(ks)\n\t\t\t}\n\t\t\t// Don't bother with relabel analysis if the scrape config\n\t\t\t// or service discovery config are empty.\n\t\t\tif len(brc) == 0 || len(bsd) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfailures = append(failures, common.Failure{\n\t\t\t\tText: fmt.Sprintf(\"job_name:\\n%s\\nrelabel_configs:\\n%s\\nkubernetes_sd_configs:\\n%s\\n\", sc.JobName, string(brc), string(bsd)),\n\t\t\t})\n\t\t\ti++\n\t\t}\n\n\t\tif len(failures) > 0 {\n\t\t\tpreAnalysis[fmt.Sprintf(\"%s/%s\", pod.Namespace, pod.Name)] = common.PreAnalysis{\n\t\t\t\tPod:            *pod,\n\t\t\t\tFailureDetails: failures,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor key, value := range preAnalysis {\n\t\tvar currentAnalysis = common.Result{\n\t\t\tKind:  kind,\n\t\t\tName:  key,\n\t\t\tError: value.FailureDetails,\n\t\t}\n\t\tparent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)\n\t\tcurrentAnalysis.ParentObject = parent\n\t\ta.Results = append(a.Results, currentAnalysis)\n\t}\n\n\treturn a.Results, nil\n}\n"
  },
  {
    "path": "pkg/kubernetes/apireference.go",
    "content": "package kubernetes\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\topenapi_v2 \"github.com/google/gnostic/openapiv2\"\n)\n\nfunc (k *K8sApiReference) GetApiDocV2(field string) string {\n\tstartPoint := \"\"\n\t// the path must be formated like \"path1.path2.path3\"\n\tpaths := strings.Split(field, \".\")\n\tgroup := strings.Split(k.ApiVersion.Group, \".\")\n\tdefinitions := k.OpenapiSchema.GetDefinitions().GetAdditionalProperties()\n\n\t// extract the startpoint by searching the highest leaf corresponding to the requested group qnd kind\n\tfor _, prop := range definitions {\n\t\tif strings.HasSuffix(prop.GetName(), fmt.Sprintf(\"%s.%s.%s\", group[0], k.ApiVersion.Version, k.Kind)) {\n\t\t\tstartPoint = prop.GetName()\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// recursively parse the definitions to find the description of the latest part of the given path\n\tdescription := k.recursePath(definitions, startPoint, paths)\n\n\treturn description\n}\n\nfunc (k *K8sApiReference) recursePath(definitions []*openapi_v2.NamedSchema, leaf string, paths []string) string {\n\tdescription := \"\"\n\n\tfor _, prop := range definitions {\n\t\t// search the requested leaf\n\t\tif prop.GetName() == leaf {\n\t\t\tfor _, addProp := range prop.GetValue().GetProperties().GetAdditionalProperties() {\n\t\t\t\t// search the additional property of the leaf corresponding the current path\n\t\t\t\tif addProp.GetName() == paths[0] {\n\t\t\t\t\t// the last path or the path is string, we get the description and we go out\n\t\t\t\t\tif len(paths) == 1 || addProp.GetValue().GetType().String() == \"value:\\\"string\\\"\" {\n\t\t\t\t\t\t// extract the path description as we are at the end of the paths\n\t\t\t\t\t\tdescription = addProp.GetValue().Description\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// the path is an object, we extract the xref\n\t\t\t\t\t\tif addProp.GetValue().GetXRef() != \"\" {\n\t\t\t\t\t\t\tsplitRef := strings.Split(addProp.GetValue().GetXRef(), \"/\")\n\t\t\t\t\t\t\treducedPaths := paths[1:]\n\t\t\t\t\t\t\tdescription = k.recursePath(definitions, splitRef[len(splitRef)-1], reducedPaths)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// the path is an array, we take the first xref from the items\n\t\t\t\t\t\tif len(addProp.GetValue().GetItems().GetSchema()) == 1 {\n\t\t\t\t\t\t\tsplitRef := strings.Split(addProp.GetValue().GetItems().GetSchema()[0].GetXRef(), \"/\")\n\t\t\t\t\t\t\treducedPaths := paths[1:]\n\t\t\t\t\t\t\tdescription = k.recursePath(definitions, splitRef[len(splitRef)-1], reducedPaths)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn description\n}\n"
  },
  {
    "path": "pkg/kubernetes/apireference_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 kubernetes\n\nimport (\n\t\"testing\"\n\n\topenapi_v2 \"github.com/google/gnostic/openapiv2\"\n\t\"github.com/stretchr/testify/require\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n)\n\nfunc TestGetApiDocV2(t *testing.T) {\n\tk8s := &K8sApiReference{\n\t\tApiVersion: schema.GroupVersion{\n\t\t\tGroup:   \"group.v1\",\n\t\t\tVersion: \"v1\",\n\t\t},\n\t\tOpenapiSchema: &openapi_v2.Document{\n\t\t\tDefinitions: &openapi_v2.Definitions{\n\t\t\t\tAdditionalProperties: []*openapi_v2.NamedSchema{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"group.v1.kind\",\n\t\t\t\t\t\tValue: &openapi_v2.Schema{\n\t\t\t\t\t\t\tTitle: \"test\",\n\t\t\t\t\t\t\tProperties: &openapi_v2.Properties{\n\t\t\t\t\t\t\t\tAdditionalProperties: []*openapi_v2.NamedSchema{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tName: \"schema1\",\n\t\t\t\t\t\t\t\t\t\tValue: &openapi_v2.Schema{\n\t\t\t\t\t\t\t\t\t\t\tTitle:       \"test\",\n\t\t\t\t\t\t\t\t\t\t\tDescription: \"schema1 description\",\n\t\t\t\t\t\t\t\t\t\t\tType: &openapi_v2.TypeItem{\n\t\t\t\t\t\t\t\t\t\t\t\tValue: []string{\"string\"},\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\t{\n\t\t\t\t\t\t\t\t\t\tName: \"schema2\",\n\t\t\t\t\t\t\t\t\t\tValue: &openapi_v2.Schema{\n\t\t\t\t\t\t\t\t\t\t\tItems: &openapi_v2.ItemsItem{\n\t\t\t\t\t\t\t\t\t\t\t\tSchema: []*openapi_v2.Schema{\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\t\tTitle: \"random-schema\",\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\tTitle:       \"test\",\n\t\t\t\t\t\t\t\t\t\t\tXRef:        \"xref\",\n\t\t\t\t\t\t\t\t\t\t\tDescription: \"schema2 description\",\n\t\t\t\t\t\t\t\t\t\t\tType: &openapi_v2.TypeItem{\n\t\t\t\t\t\t\t\t\t\t\t\tValue: []string{\"bool\"},\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\t{\n\t\t\t\t\t\tName: \"group\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tKind: \"kind\",\n\t}\n\n\ttests := []struct {\n\t\tname           string\n\t\tfield          string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\tname: \"empty field\",\n\t\t},\n\t\t{\n\t\t\tname:           \"2 schemas\",\n\t\t\tfield:          \"schema2.schema1\",\n\t\t\texpectedOutput: \"\",\n\t\t},\n\t\t{\n\t\t\tname:           \"schema1 description\",\n\t\t\tfield:          \"schema1\",\n\t\t\texpectedOutput: \"schema1 description\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\toutput := k8s.GetApiDocV2(tt.field)\n\t\t\trequire.Equal(t, tt.expectedOutput, output)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/kubernetes/kubernetes.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 kubernetes\n\nimport (\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/oidc\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\nfunc (c *Client) GetConfig() *rest.Config {\n\treturn c.Config\n}\n\nfunc (c *Client) GetClient() kubernetes.Interface {\n\treturn c.Client\n}\n\nfunc (c *Client) GetCtrlClient() ctrl.Client {\n\treturn c.CtrlClient\n}\n\nfunc (c *Client) GetDynamicClient() dynamic.Interface {\n\treturn c.DynamicClient\n}\n\nfunc NewClient(kubecontext string, kubeconfig string) (*Client, error) {\n\tvar config *rest.Config\n\tconfig, err := rest.InClusterConfig()\n\tif kubeconfig != \"\" || err != nil {\n\t\tloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()\n\n\t\tif kubeconfig != \"\" {\n\t\t\tloadingRules.ExplicitPath = kubeconfig\n\t\t}\n\n\t\tclientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(\n\t\t\tloadingRules,\n\t\t\t&clientcmd.ConfigOverrides{\n\t\t\t\tCurrentContext: kubecontext,\n\t\t\t})\n\t\t// create the clientset\n\t\tconfig, err = clientConfig.ClientConfig()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tclientSet, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctrlClient, err := ctrl.New(config, ctrl.Options{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tserverVersion, err := clientSet.ServerVersion()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdynamicClient, err := dynamic.NewForConfig(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Client{\n\t\tClient:        clientSet,\n\t\tCtrlClient:    ctrlClient,\n\t\tConfig:        config,\n\t\tServerVersion: serverVersion,\n\t\tDynamicClient: dynamicClient,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/kubernetes/types.go",
    "content": "package kubernetes\n\nimport (\n\topenapi_v2 \"github.com/google/gnostic/openapiv2\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\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\tctrl \"sigs.k8s.io/controller-runtime/pkg/client\"\n)\n\ntype Client struct {\n\tClient        kubernetes.Interface\n\tCtrlClient    ctrl.Client\n\tConfig        *rest.Config\n\tServerVersion *version.Info\n\tDynamicClient dynamic.Interface\n}\n\ntype K8sApiReference struct {\n\tApiVersion    schema.GroupVersion\n\tKind          string\n\tOpenapiSchema *openapi_v2.Document\n}\n"
  },
  {
    "path": "pkg/server/README.md",
    "content": "# K8sGPT MCP Server\n\nThis directory contains the implementation of the Mission Control Protocol (MCP) server for K8sGPT. The MCP server allows K8sGPT to be integrated with other tools that support the MCP protocol.\n\n## Components\n\n- `mcp.go`: The main MCP server implementation\n- `server.go`: The HTTP server implementation\n- `tools.go`: Tool definitions for the MCP server\n\n## Features\n\nThe MCP server provides the following features:\n\n1. **Analyze Kubernetes Resources**: Analyze Kubernetes resources in a cluster\n2. **Get Cluster Information**: Retrieve information about the Kubernetes cluster\n\n## Usage\n\nTo use the MCP server, you need to:\n\n1. Initialize the MCP server with a Kubernetes client\n2. Start the server\n3. Connect to the server using an MCP client\n\nExample:\n\n```go\nclient, err := kubernetes.NewForConfig(config)\nif err != nil {\n    log.Fatalf(\"Failed to create Kubernetes client: %v\", err)\n}\n\nmcpServer := server.NewMCPServer(client)\nif err := mcpServer.Start(); err != nil {\n    log.Fatalf(\"Failed to start MCP server: %v\", err)\n}\n```\n\n## Integration\n\nThe MCP server can be integrated with other tools that support the MCP protocol, such as:\n\n- Mission Control\n- Other MCP-compatible tools\n\n## License\n\nThis code is licensed under the Apache License 2.0.\n"
  },
  {
    "path": "pkg/server/analyze/analyze.go",
    "content": "package analyze\n\nimport (\n\t\"context\"\n\tjson \"encoding/json\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analysis\"\n)\n\nfunc (h *Handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (\n\t*schemav1.AnalyzeResponse,\n\terror,\n) {\n\tif i.Output == \"\" {\n\t\ti.Output = \"json\"\n\t}\n\n\tif int(i.MaxConcurrency) == 0 {\n\t\ti.MaxConcurrency = 10\n\t}\n\n\tconfig, err := analysis.NewAnalysis(\n\t\ti.Backend,\n\t\ti.Language,\n\t\ti.Filters,\n\t\ti.Namespace,\n\t\ti.LabelSelector,\n\t\ti.Nocache,\n\t\ti.Explain,\n\t\tint(i.MaxConcurrency),\n\t\tfalse,      // Kubernetes Doc disabled in server mode\n\t\tfalse,      // Interactive mode disabled in server mode\n\t\t[]string{}, //TODO: add custom http headers in server mode\n\t\tfalse,      // with stats disable\n\t)\n\tif err != nil {\n\t\treturn &schemav1.AnalyzeResponse{}, err\n\t}\n\tconfig.Context = ctx // Replace context for correct timeouts.\n\tdefer config.Close()\n\n\tif config.CustomAnalyzersAreAvailable() {\n\t\tconfig.RunCustomAnalysis()\n\t}\n\tconfig.RunAnalysis()\n\n\tif i.Explain {\n\t\terr := config.GetAIResults(i.Output, i.Anonymize)\n\t\tif err != nil {\n\t\t\treturn &schemav1.AnalyzeResponse{}, err\n\t\t}\n\t}\n\n\tout, err := config.PrintOutput(i.Output)\n\tif err != nil {\n\t\treturn &schemav1.AnalyzeResponse{}, err\n\t}\n\tvar obj schemav1.AnalyzeResponse\n\n\terr = json.Unmarshal(out, &obj)\n\tif err != nil {\n\t\treturn &schemav1.AnalyzeResponse{}, err\n\t}\n\n\treturn &obj, nil\n}\n"
  },
  {
    "path": "pkg/server/analyze/handler.go",
    "content": "package analyze\n\nimport rpc \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc\"\n\ntype Handler struct {\n\trpc.UnimplementedServerAnalyzerServiceServer\n}\n"
  },
  {
    "path": "pkg/server/client_example/README.md",
    "content": "# K8sGPT MCP Client Example\n\nThis directory contains an example of how to use the K8sGPT MCP client in a real-world scenario.\n\n## Prerequisites\n\n- Go 1.16 or later\n- Access to a Kubernetes cluster\n- `kubectl` configured to access your cluster\n\n## Building the Example\n\nTo build the example, run:\n\n```bash\ngo build -o mcp-client-example\n```\n\n## Running the Example\n\nTo run the example, use the following command:\n\n```bash\n./mcp-client-example --kubeconfig=/path/to/kubeconfig --namespace=default\n```\n\n### Command-line Flags\n\n- `--kubeconfig`: Path to the kubeconfig file (optional, defaults to the standard location)\n- `--namespace`: Kubernetes namespace to analyze (optional)\n\n## Example Output\n\nWhen you run the example, you should see output similar to the following:\n\n```\nStarting MCP client...\n```\n\nThe client will continue running until you press Ctrl+C to stop it.\n\n## Integration with Mission Control\n\nTo integrate this example with Mission Control, you need to:\n\n1. Start the MCP client using the example\n2. Configure Mission Control to connect to the MCP client\n3. Use Mission Control to analyze your Kubernetes cluster\n\n## Troubleshooting\n\nIf you encounter any issues, check the following:\n\n1. Ensure that your Kubernetes cluster is accessible\n2. Verify that your kubeconfig file is valid\n3. Check that the namespace you specified exists\n\n## License\n\nThis code is licensed under the Apache License 2.0. "
  },
  {
    "path": "pkg/server/client_example/main.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\n// AnalyzeRequest represents the input parameters for the analyze tool\ntype AnalyzeRequest struct {\n\tNamespace       string   `json:\"namespace,omitempty\"`\n\tBackend         string   `json:\"backend,omitempty\"`\n\tLanguage        string   `json:\"language,omitempty\"`\n\tFilters         []string `json:\"filters,omitempty\"`\n\tLabelSelector   string   `json:\"labelSelector,omitempty\"`\n\tNoCache         bool     `json:\"noCache,omitempty\"`\n\tExplain         bool     `json:\"explain,omitempty\"`\n\tMaxConcurrency  int      `json:\"maxConcurrency,omitempty\"`\n\tWithDoc         bool     `json:\"withDoc,omitempty\"`\n\tInteractiveMode bool     `json:\"interactiveMode,omitempty\"`\n\tCustomHeaders   []string `json:\"customHeaders,omitempty\"`\n\tWithStats       bool     `json:\"withStats,omitempty\"`\n}\n\n// JSONRPCResponse represents the JSON-RPC response format\ntype JSONRPCResponse struct {\n\tJSONRPC string `json:\"jsonrpc\"`\n\tID      int    `json:\"id\"`\n\tResult  struct {\n\t\tContent []struct {\n\t\t\tText string `json:\"text\"`\n\t\t\tType string `json:\"type\"`\n\t\t} `json:\"content\"`\n\t} `json:\"result,omitempty\"`\n\tError *struct {\n\t\tCode    int    `json:\"code\"`\n\t\tMessage string `json:\"message\"`\n\t} `json:\"error,omitempty\"`\n}\n\nfunc main() {\n\t// Parse command line flags\n\tserverPort := flag.String(\"port\", \"8089\", \"Port of the MCP server\")\n\tnamespace := flag.String(\"namespace\", \"\", \"Kubernetes namespace to analyze\")\n\tbackend := flag.String(\"backend\", \"\", \"AI backend to use\")\n\tlanguage := flag.String(\"language\", \"english\", \"Language for analysis\")\n\tflag.Parse()\n\n\t// Create analyze request\n\treq := AnalyzeRequest{\n\t\tNamespace:      *namespace,\n\t\tBackend:        *backend,\n\t\tLanguage:       *language,\n\t\tExplain:        true,\n\t\tMaxConcurrency: 10,\n\t}\n\n\t// Note: req is now used directly in the JSON-RPC request\n\n\t// Create HTTP client with timeout\n\tclient := &http.Client{\n\t\tTimeout: 5 * time.Minute,\n\t}\n\n\t// First, initialize the session\n\tinitRequest := map[string]interface{}{\n\t\t\"jsonrpc\": \"2.0\",\n\t\t\"id\":      1,\n\t\t\"method\":  \"initialize\",\n\t\t\"params\": map[string]interface{}{\n\t\t\t\"protocolVersion\": \"2025-03-26\",\n\t\t\t\"capabilities\": map[string]interface{}{\n\t\t\t\t\"tools\":     map[string]interface{}{},\n\t\t\t\t\"resources\": map[string]interface{}{},\n\t\t\t\t\"prompts\":   map[string]interface{}{},\n\t\t\t},\n\t\t\t\"clientInfo\": map[string]interface{}{\n\t\t\t\t\"name\":    \"k8sgpt-client\",\n\t\t\t\t\"version\": \"1.0.0\",\n\t\t\t},\n\t\t},\n\t}\n\n\tinitData, err := json.Marshal(initRequest)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to marshal init request: %v\", err)\n\t}\n\n\t// Send initialization request\n\tinitResp, err := client.Post(\n\t\tfmt.Sprintf(\"http://localhost:%s/mcp\", *serverPort),\n\t\t\"application/json\",\n\t\tbytes.NewBuffer(initData),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to send init request: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err := initResp.Body.Close(); err != nil {\n\t\t\tlog.Printf(\"Error closing init response body: %v\", err)\n\t\t}\n\t}()\n\n\t// Extract session ID from response headers\n\tsessionID := initResp.Header.Get(\"Mcp-Session-Id\")\n\tif sessionID == \"\" {\n\t\tlog.Println(\"Warning: No session ID received from server\")\n\t}\n\n\t// Create JSON-RPC request for analyze\n\tjsonRPCRequest := map[string]interface{}{\n\t\t\"jsonrpc\": \"2.0\",\n\t\t\"id\":      2,\n\t\t\"method\":  \"tools/call\",\n\t\t\"params\": map[string]interface{}{\n\t\t\t\"name\":      \"analyze\",\n\t\t\t\"arguments\": req,\n\t\t},\n\t}\n\n\t// Convert to JSON\n\tjsonRPCData, err := json.Marshal(jsonRPCRequest)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to marshal JSON-RPC request: %v\", err)\n\t}\n\n\t// Create request with session ID if available\n\thttpReq, err := http.NewRequest(\"POST\", fmt.Sprintf(\"http://localhost:%s/mcp\", *serverPort), bytes.NewBuffer(jsonRPCData))\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create request: %v\", err)\n\t}\n\n\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\thttpReq.Header.Set(\"Accept\", \"application/json,text/event-stream\")\n\tif sessionID != \"\" {\n\t\thttpReq.Header.Set(\"Mcp-Session-Id\", sessionID)\n\t}\n\n\t// Send request to MCP server\n\tresp, err := client.Do(httpReq)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to send request: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tlog.Printf(\"Error closing response body: %v\", err)\n\t\t}\n\t}()\n\n\t// Read and print raw response for debugging\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to read response body: %v\", err)\n\t}\n\tfmt.Printf(\"Raw response: %s\\n\", string(body))\n\n\t// Parse response\n\tvar jsonRPCResp JSONRPCResponse\n\tif err := json.Unmarshal(body, &jsonRPCResp); err != nil {\n\t\tlog.Fatalf(\"Failed to decode response: %v\", err)\n\t}\n\n\t// Print results\n\tfmt.Println(\"Analysis Results:\")\n\tif jsonRPCResp.Error != nil {\n\t\tfmt.Printf(\"Error: %s (code: %d)\\n\", jsonRPCResp.Error.Message, jsonRPCResp.Error.Code)\n\t} else if len(jsonRPCResp.Result.Content) > 0 {\n\t\tfmt.Println(jsonRPCResp.Result.Content[0].Text)\n\t} else {\n\t\tfmt.Println(\"No results returned\")\n\t}\n}\n"
  },
  {
    "path": "pkg/server/config/config.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/cache\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/custom\"\n\t\"github.com/spf13/viper\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\nconst (\n\tnotUsedBucket        = \"\"\n\tnotUsedRegion        = \"\"\n\tnotUsedEndpoint      = \"\"\n\tnotUsedStorageAcc    = \"\"\n\tnotUsedContainerName = \"\"\n\tnotUsedProjectId     = \"\"\n\tnotUsedInsecure      = false\n)\n\n// ApplyConfig applies the configuration changes from the request\nfunc (h *Handler) ApplyConfig(ctx context.Context, i *schemav1.AddConfigRequest) error {\n\tif i.CustomAnalyzers != nil {\n\t\t// We need to add the custom analyzers to the viper config and save them\n\t\tvar customAnalyzers = make([]custom.CustomAnalyzer, 0)\n\t\tif err := viper.UnmarshalKey(\"custom_analyzers\", &customAnalyzers); err != nil {\n\t\t\treturn err\n\t\t} else {\n\t\t\t// If there are analyzers are already in the config we will append the ones with new names\n\t\t\tfor _, ca := range i.CustomAnalyzers {\n\t\t\t\texists := false\n\t\t\t\tfor _, c := range customAnalyzers {\n\t\t\t\t\tif c.Name == ca.Name {\n\t\t\t\t\t\texists = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !exists {\n\t\t\t\t\tcustomAnalyzers = append(customAnalyzers, custom.CustomAnalyzer{\n\t\t\t\t\t\tName: ca.Name,\n\t\t\t\t\t\tConnection: custom.Connection{\n\t\t\t\t\t\t\tUrl:  ca.Connection.Url,\n\t\t\t\t\t\t\tPort: ca.Connection.Port,\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// save the config\n\t\t\tviper.Set(\"custom_analyzers\", customAnalyzers)\n\t\t\tif err := viper.WriteConfig(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tif i.Cache != nil {\n\t\tvar err error\n\t\tvar remoteCache cache.CacheProvider\n\n\t\tswitch i.Cache.GetCacheType().(type) {\n\t\tcase *schemav1.Cache_AzureCache:\n\t\t\tremoteCache, err = cache.NewCacheProvider(\"azure\", notUsedBucket, notUsedRegion, notUsedEndpoint, i.Cache.GetAzureCache().StorageAccount, i.Cache.GetAzureCache().ContainerName, notUsedProjectId, notUsedInsecure)\n\t\tcase *schemav1.Cache_S3Cache:\n\t\t\tremoteCache, err = cache.NewCacheProvider(\"s3\", i.Cache.GetS3Cache().BucketName, i.Cache.GetS3Cache().Region, i.Cache.GetS3Cache().Endpoint, notUsedStorageAcc, notUsedContainerName, notUsedProjectId, i.Cache.GetS3Cache().Insecure)\n\t\tcase *schemav1.Cache_GcsCache:\n\t\t\tremoteCache, err = cache.NewCacheProvider(\"gcs\", i.Cache.GetGcsCache().BucketName, i.Cache.GetGcsCache().Region, notUsedEndpoint, notUsedStorageAcc, notUsedContainerName, i.Cache.GetGcsCache().GetProjectId(), notUsedInsecure)\n\t\tcase *schemav1.Cache_InterplexCache:\n\t\t\tremoteCache, err = cache.NewCacheProvider(\"interplex\", notUsedBucket, notUsedRegion, i.Cache.GetInterplexCache().Endpoint, notUsedStorageAcc, notUsedContainerName, notUsedProjectId, notUsedInsecure)\n\t\tdefault:\n\t\t\treturn status.Error(codes.InvalidArgument, \"Invalid cache configuration\")\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = cache.AddRemoteCache(remoteCache)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (h *Handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error) {\n\tresp, err := h.syncIntegration(ctx, i)\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\n\tif err := h.ApplyConfig(ctx, i); err != nil {\n\t\treturn resp, err\n\t}\n\n\treturn resp, nil\n}\n\nfunc (h *Handler) RemoveConfig(ctx context.Context, i *schemav1.RemoveConfigRequest) (*schemav1.RemoveConfigResponse, error,\n) {\n\terr := cache.RemoveRemoteCache()\n\tif err != nil {\n\t\treturn &schemav1.RemoveConfigResponse{}, err\n\t}\n\n\t// Remove any integrations is a TBD as it would be nice to make this more granular\n\t// Currently integrations can be removed in the AddConfig sync\n\n\treturn &schemav1.RemoveConfigResponse{\n\t\tStatus: \"Successfully removed the remote cache\",\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/server/config/handler.go",
    "content": "package config\n\nimport (\n\trpc \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc\"\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"context\"\n)\n\ntype Handler struct {\n\trpc.UnimplementedServerConfigServiceServer\n}\n\nfunc (h *Handler) Shutdown(ctx context.Context, request *schemav1.ShutdownRequest) (*schemav1.ShutdownResponse, error) {\n\t//TODO implement me\n\tpanic(\"implement me\")\n}\n"
  },
  {
    "path": "pkg/server/config/integration.go",
    "content": "package config\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// syncIntegration is aware of the following events\n// A new integration added\n// An integration removed from the Integration block\nfunc (h *Handler) syncIntegration(ctx context.Context,\n\ti *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error,\n) {\n\n\tfmt.Println(\"WARNING: syncIntegration is deprecated.\")\n\n\tresponse := &schemav1.AddConfigResponse{}\n\tintegrationProvider := integration.NewIntegration()\n\tif i.Integrations == nil {\n\t\t// If there are locally activate integrations, disable them\n\t\terr := h.deactivateAllIntegrations(integrationProvider)\n\t\tif err != nil {\n\t\t\treturn response, status.Error(codes.NotFound, \"deactivation error\")\n\t\t}\n\t\treturn response, nil\n\t}\n\n\t// Warning: This code is an example of an integration modifying the active filter list\n\t// This integration is no longer part of K8sGPT due to compatibility issues\n\n\t//coreFilters, _, _ := analyzer.ListFilters()\n\t// Update filters\n\t//activeFilters := viper.GetStringSlice(\"active_filters\")\n\t//if len(activeFilters) == 0 {\n\t//\tactiveFilters = coreFilters\n\t//}\n\t//var err error = status.Error(codes.OK, \"\")\n\t//if err != nil {\n\t//\tfmt.Println(err)\n\t//}\n\t//deactivateFunc := func(integrationRef integration.IIntegration) error {\n\t//\tnamespace, err := integrationRef.GetNamespace()\n\t//\tif err != nil {\n\t//\t\treturn err\n\t//\t}\n\t//\terr = integrationProvider.Deactivate(trivyName, namespace)\n\t//\tif err != nil {\n\t//\t\treturn status.Error(codes.NotFound, \"integration already deactivated\")\n\t//\t}\n\t//\treturn nil\n\t//}\n\t//integrationRef, err := integrationProvider.Get(trivyName)\n\t//if err != nil {\n\t//\treturn response, status.Error(codes.NotFound, \"provider get failure\")\n\t//}\n\t//if i.Integrations.Trivy != nil {\n\t//\tswitch i.Integrations.Trivy.Enabled {\n\t//\tcase true:\n\t//\t\tif b, err := integrationProvider.IsActivate(trivyName); err != nil {\n\t//\t\t\treturn response, status.Error(codes.Internal, \"integration activation error\")\n\t//\t\t} else {\n\t//\t\t\tif !b {\n\t//\t\t\t\terr := integrationProvider.Activate(trivyName, i.Integrations.Trivy.Namespace,\n\t//\t\t\t\t\tactiveFilters, i.Integrations.Trivy.SkipInstall)\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} else {\n\t//\t\t\t\treturn response, status.Error(codes.AlreadyExists, \"integration already active\")\n\t//\t\t\t}\n\t//\t\t}\n\t//\tcase false:\n\t//\t\terr = deactivateFunc(integrationRef)\n\t//\t\tif err != nil {\n\t//\t\t\treturn nil, err\n\t//\t\t}\n\t//\t\t// This break is included purely for static analysis to pass\n\t//\t}\n\t//} else {\n\t//\t// If Trivy has been removed, disable it\n\t//\terr = deactivateFunc(integrationRef)\n\t//\tif err != nil {\n\t//\t\treturn nil, err\n\t//\t}\n\t//}\n\n\treturn response, nil\n}\n\nfunc (h *Handler) ListIntegrations(ctx context.Context, req *schemav1.ListIntegrationsRequest) (*schemav1.ListIntegrationsResponse, error) {\n\n\tfmt.Println(\"WARNING: ListIntegrations is deprecated.\")\n\n\t//integrationProvider := integration.NewIntegration()\n\t// Update the requester with the status of Trivy\n\t//trivy, err := integrationProvider.Get(trivyName)\n\t//active := trivy.IsActivate()\n\t//var skipInstall bool\n\t//var namespace string = \"\"\n\t//if active {\n\t//\tnamespace, err = trivy.GetNamespace()\n\t//\tif err != nil {\n\t//\t\treturn nil, status.Error(codes.NotFound, \"namespace not found\")\n\t//\t}\n\t//\tif namespace == \"\" {\n\t//\t\tskipInstall = true\n\t//\t}\n\t//}\n\t//\n\t//if err != nil {\n\t//\treturn nil, status.Error(codes.NotFound, \"trivy integration\")\n\t//}\n\tresp := &schemav1.ListIntegrationsResponse{\n\t\t//Trivy: &schemav1.Trivy{\n\t\t//\tEnabled:     active,\n\t\t//\tNamespace:   namespace,\n\t\t//\tSkipInstall: skipInstall,\n\t\t//},\n\t}\n\n\treturn resp, nil\n}\n\nfunc (*Handler) deactivateAllIntegrations(integrationProvider *integration.Integration) error {\n\n\tfmt.Println(\"WARNING: deactivateIntegrations is deprecated.\")\n\tintegrations := integrationProvider.List()\n\tfor _, i := range integrations {\n\t\tb, _ := integrationProvider.IsActivate(i)\n\t\tif b {\n\t\t\tin, err := integrationProvider.Get(i)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tnamespace, err := in.GetNamespace()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\tif namespace != \"\" {\n\t\t\t\t\terr := integrationProvider.Deactivate(i, 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} else {\n\t\t\t\t\tfmt.Printf(\"Skipping deactivation of %s, not installed\\n\", i)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/server/example/main.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/server\"\n\t\"go.uber.org/zap\"\n)\n\nfunc main() {\n\t// Parse command line flags\n\tport := flag.String(\"port\", \"8089\", \"Port to run the MCP server on\")\n\tuseHTTP := flag.Bool(\"http\", false, \"Enable HTTP mode for MCP server\")\n\tflag.Parse()\n\n\t// Initialize zap logger\n\tlogger, err := zap.NewProduction()\n\tif err != nil {\n\t\tlog.Fatalf(\"Error creating logger: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err := logger.Sync(); err != nil {\n\t\t\tlog.Printf(\"Error syncing logger: %v\", err)\n\t\t}\n\t}()\n\n\t// Create AI provider\n\taiProvider := &ai.AIProvider{\n\t\tName:     \"openai\",\n\t\tPassword: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tModel:    \"gpt-3.5-turbo\",\n\t}\n\n\t// Create and start MCP server\n\tmcpServer, err := server.NewMCPServer(*port, aiProvider, *useHTTP, logger)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error creating MCP server: %v\", err)\n\t}\n\n\t// Start the server in a goroutine\n\tgo func() {\n\t\tif err := mcpServer.Start(); err != nil {\n\t\t\tlog.Fatalf(\"Error starting MCP server: %v\", err)\n\t\t}\n\t}()\n\n\t// Handle graceful shutdown\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)\n\t<-sigChan\n\n\t// Cleanup\n\tif err := mcpServer.Close(); err != nil {\n\t\tlog.Printf(\"Error closing MCP server: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/server/log.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/peer\"\n\t\"google.golang.org/grpc/status\"\n)\n\nfunc LogInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {\n\treturn func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {\n\t\tstart := time.Now()\n\n\t\t// Call the handler to execute the gRPC request\n\t\tresponse, err := handler(ctx, req)\n\n\t\tduration := time.Since(start).Milliseconds()\n\t\tfields := []zap.Field{\n\t\t\tzap.Int64(\"duration_ms\", duration),\n\t\t\tzap.String(\"method\", info.FullMethod),\n\t\t\tzap.Any(\"request\", req),\n\t\t}\n\t\t// Get the remote address from the context\n\t\tpeer, ok := peer.FromContext(ctx)\n\t\tif ok {\n\t\t\tfields = append(fields, zap.String(\"remote_addr\", peer.Addr.String()))\n\t\t}\n\n\t\tif err != nil {\n\t\t\tfields = append(fields, zap.Int32(\"status_code\", int32(status.Code(err))))\n\t\t}\n\t\tmessage := \"request completed\"\n\t\tif err != nil {\n\t\t\tmessage = fmt.Sprintf(\"request failed. %s\", err.Error())\n\t\t}\n\t\tlogRequest(logger, fields, int(status.Code(err)), message)\n\n\t\treturn response, err\n\t}\n}\n\nfunc logRequest(logger *zap.Logger, fields []zap.Field, statusCode int, message string) {\n\tif statusCode >= 400 {\n\t\tlogger.Error(message, fields...)\n\t} else {\n\t\tlogger.Info(message, fields...)\n\t}\n}\n"
  },
  {
    "path": "pkg/server/mcp.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"regexp\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analysis\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/server/config\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"github.com/mark3labs/mcp-go/server\"\n\t\"github.com/spf13/viper\"\n\t\"go.uber.org/zap\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// K8sGptMCPServer represents an MCP server for k8sgpt\ntype K8sGptMCPServer struct {\n\tserver      *server.MCPServer\n\tport        string\n\taiProvider  *ai.AIProvider\n\tuseHTTP     bool\n\tlogger      *zap.Logger\n\thttpServer  *server.StreamableHTTPServer\n\tstdioServer *server.StdioServer\n}\n\nfunc NewMCPServer(port string, aiProvider *ai.AIProvider, useHTTP bool, logger *zap.Logger) (*K8sGptMCPServer, error) {\n\topts := []server.ServerOption{\n\t\tserver.WithToolCapabilities(true),\n\t\tserver.WithResourceCapabilities(true, false),\n\t\tserver.WithPromptCapabilities(false),\n\t}\n\n\t// Create the MCP server\n\tmcpServer := server.NewMCPServer(\"k8sgpt\", \"1.0.0\", opts...)\n\tvar k8sGptMCPServer = &K8sGptMCPServer{\n\t\tserver:     mcpServer,\n\t\tport:       port,\n\t\taiProvider: aiProvider,\n\t\tuseHTTP:    useHTTP,\n\t\tlogger:     logger,\n\t}\n\n\t// Register tools and resources immediately\n\tif err := k8sGptMCPServer.registerToolsAndResources(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to register tools and resources: %v\", err)\n\t}\n\n\tif useHTTP {\n\t\t// Create HTTP server with streamable transport\n\t\thttpOpts := []server.StreamableHTTPOption{\n\t\t\tserver.WithLogger(&zapLoggerAdapter{logger: logger}),\n\t\t\t// Enable stateless mode for one-off tool invocations without session management\n\t\t\tserver.WithStateLess(true),\n\t\t}\n\n\t\thttpServer := server.NewStreamableHTTPServer(mcpServer, httpOpts...)\n\n\t\t// Launch the HTTP server directly\n\t\tgo func() {\n\t\t\tlogger.Info(\"Starting MCP HTTP server\", zap.String(\"port\", port))\n\t\t\tif err := httpServer.Start(\":\" + port); err != nil {\n\t\t\t\tlogger.Fatal(\"MCP HTTP server failed\", zap.Error(err))\n\t\t\t}\n\t\t}()\n\n\t\treturn &K8sGptMCPServer{\n\t\t\tserver:     mcpServer,\n\t\t\tport:       port,\n\t\t\taiProvider: aiProvider,\n\t\t\tuseHTTP:    useHTTP,\n\t\t\tlogger:     logger,\n\t\t\thttpServer: httpServer,\n\t\t}, nil\n\t} else {\n\t\t// Create stdio server\n\t\tstdioServer := server.NewStdioServer(mcpServer)\n\n\t\treturn &K8sGptMCPServer{\n\t\t\tserver:      mcpServer,\n\t\t\tport:        port,\n\t\t\taiProvider:  aiProvider,\n\t\t\tuseHTTP:     useHTTP,\n\t\t\tlogger:      logger,\n\t\t\tstdioServer: stdioServer,\n\t\t}, nil\n\t}\n}\n\n// Start starts the MCP server\nfunc (s *K8sGptMCPServer) Start() error {\n\tif s.server == nil {\n\t\treturn fmt.Errorf(\"server not initialized\")\n\t}\n\t// Register prompts\n\tif err := s.registerPrompts(); err != nil {\n\t\treturn fmt.Errorf(\"failed to register prompts: %v\", err)\n\t}\n\t// Register resources\n\tif err := s.registerResources(); err != nil {\n\t\treturn fmt.Errorf(\"failed to register resources: %v\", err)\n\t}\n\n\t// Start the server based on transport type\n\tif s.useHTTP {\n\t\t// HTTP server is already running in a goroutine\n\t\treturn nil\n\t} else {\n\t\t// Start stdio server (this will block)\n\t\treturn server.ServeStdio(s.server)\n\t}\n}\n\nfunc (s *K8sGptMCPServer) registerToolsAndResources() error {\n\t// Register analyze tool with proper JSON schema\n\tanalyzeTool := mcp.NewTool(\"analyze\",\n\t\tmcp.WithDescription(\"Analyze Kubernetes resources for issues and problems\"),\n\t\tmcp.WithString(\"namespace\",\n\t\t\tmcp.Description(\"Kubernetes namespace to analyze (empty for all namespaces)\"),\n\t\t),\n\t\tmcp.WithString(\"backend\",\n\t\t\tmcp.Description(\"AI backend to use for analysis (e.g., openai, azure, localai)\"),\n\t\t),\n\t\tmcp.WithBoolean(\"explain\",\n\t\t\tmcp.Description(\"Provide detailed explanations for issues\"),\n\t\t),\n\t\tmcp.WithArray(\"filters\",\n\t\t\tmcp.Description(\"Provide filters to narrow down the analysis (e.g. ['Pods', 'Deployments'])\"),\n\t\t\t// without below line MCP server fails with Google Agent Development Kit (ADK), interestingly works fine with mcpinspector\n\t\t\tmcp.WithStringItems(),\n\t\t),\n\t)\n\ts.server.AddTool(analyzeTool, s.handleAnalyze)\n\n\t// Register cluster info tool (no parameters needed)\n\tclusterInfoTool := mcp.NewTool(\"cluster-info\",\n\t\tmcp.WithDescription(\"Get Kubernetes cluster information and version\"),\n\t)\n\ts.server.AddTool(clusterInfoTool, s.handleClusterInfo)\n\n\t// Register config tool with proper JSON schema\n\tconfigTool := mcp.NewTool(\"config\",\n\t\tmcp.WithDescription(\"Configure K8sGPT settings including custom analyzers and cache\"),\n\t\tmcp.WithObject(\"customAnalyzers\",\n\t\t\tmcp.Description(\"Custom analyzer configurations\"),\n\t\t\tmcp.Properties(map[string]any{\n\t\t\t\t\"name\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Name of the custom analyzer\",\n\t\t\t\t},\n\t\t\t\t\"connection\": map[string]any{\n\t\t\t\t\t\"type\": \"object\",\n\t\t\t\t\t\"properties\": map[string]any{\n\t\t\t\t\t\t\"url\": map[string]any{\n\t\t\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\t\t\"description\": \"URL of the custom analyzer service\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"port\": map[string]any{\n\t\t\t\t\t\t\t\"type\":        \"integer\",\n\t\t\t\t\t\t\t\"description\": \"Port of the custom analyzer 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\t\tmcp.WithObject(\"cache\",\n\t\t\tmcp.Description(\"Cache configuration\"),\n\t\t\tmcp.Properties(map[string]any{\n\t\t\t\t\"type\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Cache type (s3, azure, gcs)\",\n\t\t\t\t\t\"enum\":        []string{\"s3\", \"azure\", \"gcs\"},\n\t\t\t\t},\n\t\t\t\t\"bucketName\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Bucket name for S3/GCS cache\",\n\t\t\t\t},\n\t\t\t\t\"region\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Region for S3/GCS cache\",\n\t\t\t\t},\n\t\t\t\t\"endpoint\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Custom endpoint for S3 cache\",\n\t\t\t\t},\n\t\t\t\t\"insecure\": map[string]any{\n\t\t\t\t\t\"type\":        \"boolean\",\n\t\t\t\t\t\"description\": \"Use insecure connection for cache\",\n\t\t\t\t},\n\t\t\t\t\"storageAccount\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Storage account for Azure cache\",\n\t\t\t\t},\n\t\t\t\t\"containerName\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Container name for Azure cache\",\n\t\t\t\t},\n\t\t\t\t\"projectId\": map[string]any{\n\t\t\t\t\t\"type\":        \"string\",\n\t\t\t\t\t\"description\": \"Project ID for GCS cache\",\n\t\t\t\t},\n\t\t\t}),\n\t\t),\n\t)\n\ts.server.AddTool(configTool, s.handleConfig)\n\n\t// Register resource listing tools\n\tlistResourcesTool := mcp.NewTool(\"list-resources\",\n\t\tmcp.WithDescription(\"List Kubernetes resources of a specific type (pods, deployments, services, nodes, etc.)\"),\n\t\tmcp.WithString(\"resourceType\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"Type of resource to list (e.g., pods, deployments, services, nodes, jobs, etc.)\"),\n\t\t),\n\t\tmcp.WithString(\"namespace\",\n\t\t\tmcp.Description(\"Namespace to list resources from (empty for all or cluster-scoped resources)\"),\n\t\t),\n\t\tmcp.WithString(\"labelSelector\",\n\t\t\tmcp.Description(\"Label selector to filter resources (e.g., 'app=myapp')\"),\n\t\t),\n\t)\n\ts.server.AddTool(listResourcesTool, s.handleListResources)\n\n\t// Register get resource tool\n\tgetResourceTool := mcp.NewTool(\"get-resource\",\n\t\tmcp.WithDescription(\"Get detailed information about a specific Kubernetes resource\"),\n\t\tmcp.WithString(\"resourceType\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"Type of resource (e.g., pod, deployment, service)\"),\n\t\t),\n\t\tmcp.WithString(\"name\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"Name of the resource\"),\n\t\t),\n\t\tmcp.WithString(\"namespace\",\n\t\t\tmcp.Description(\"Namespace of the resource (required for namespaced resources)\"),\n\t\t),\n\t)\n\ts.server.AddTool(getResourceTool, s.handleGetResource)\n\n\t// Register list namespaces tool\n\tlistNamespacesTool := mcp.NewTool(\"list-namespaces\",\n\t\tmcp.WithDescription(\"List all namespaces in the cluster\"),\n\t)\n\ts.server.AddTool(listNamespacesTool, s.handleListNamespaces)\n\n\t// Register list events tool\n\tlistEventsTool := mcp.NewTool(\"list-events\",\n\t\tmcp.WithDescription(\"List Kubernetes events for debugging and troubleshooting\"),\n\t\tmcp.WithString(\"namespace\",\n\t\t\tmcp.Description(\"Namespace to list events from (empty for all namespaces)\"),\n\t\t),\n\t\tmcp.WithString(\"involvedObjectName\",\n\t\t\tmcp.Description(\"Filter events by involved object name (e.g., pod name)\"),\n\t\t),\n\t\tmcp.WithString(\"involvedObjectKind\",\n\t\t\tmcp.Description(\"Filter events by involved object kind (e.g., Pod, Deployment)\"),\n\t\t),\n\t\tmcp.WithNumber(\"limit\",\n\t\t\tmcp.Description(\"Maximum number of events to return (default: 100)\"),\n\t\t),\n\t)\n\ts.server.AddTool(listEventsTool, s.handleListEvents)\n\n\t// Register get logs tool\n\tgetLogsTool := mcp.NewTool(\"get-logs\",\n\t\tmcp.WithDescription(\"Get logs from a pod container\"),\n\t\tmcp.WithString(\"podName\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"Name of the pod\"),\n\t\t),\n\t\tmcp.WithString(\"namespace\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"Namespace of the pod\"),\n\t\t),\n\t\tmcp.WithString(\"container\",\n\t\t\tmcp.Description(\"Container name (if pod has multiple containers)\"),\n\t\t),\n\t\tmcp.WithBoolean(\"previous\",\n\t\t\tmcp.Description(\"Get logs from previous terminated container\"),\n\t\t),\n\t\tmcp.WithNumber(\"tailLines\",\n\t\t\tmcp.Description(\"Number of lines from the end of logs (default: 100)\"),\n\t\t),\n\t\tmcp.WithNumber(\"sinceSeconds\",\n\t\t\tmcp.Description(\"Return logs newer than this many seconds\"),\n\t\t),\n\t)\n\ts.server.AddTool(getLogsTool, s.handleGetLogs)\n\n\t// Register filter management tools\n\tlistFiltersTool := mcp.NewTool(\"list-filters\",\n\t\tmcp.WithDescription(\"List all available and active analyzers/filters in k8sgpt\"),\n\t)\n\ts.server.AddTool(listFiltersTool, s.handleListFilters)\n\n\taddFiltersTool := mcp.NewTool(\"add-filters\",\n\t\tmcp.WithDescription(\"Add filters to enable specific analyzers\"),\n\t\tmcp.WithArray(\"filters\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"List of filter names to add (e.g., ['Pod', 'Service', 'Deployment'])\"),\n\t\t\tmcp.WithStringItems(),\n\t\t),\n\t)\n\ts.server.AddTool(addFiltersTool, s.handleAddFilters)\n\n\tremoveFiltersTool := mcp.NewTool(\"remove-filters\",\n\t\tmcp.WithDescription(\"Remove filters to disable specific analyzers\"),\n\t\tmcp.WithArray(\"filters\",\n\t\t\tmcp.Required(),\n\t\t\tmcp.Description(\"List of filter names to remove\"),\n\t\t\tmcp.WithStringItems(),\n\t\t),\n\t)\n\ts.server.AddTool(removeFiltersTool, s.handleRemoveFilters)\n\n\t// Register integration management tools\n\tlistIntegrationsTool := mcp.NewTool(\"list-integrations\",\n\t\tmcp.WithDescription(\"List available integrations (Prometheus, AWS, Keda, Kyverno, etc.)\"),\n\t)\n\ts.server.AddTool(listIntegrationsTool, s.handleListIntegrations)\n\n\treturn nil\n}\n\n// AnalyzeRequest represents the input parameters for the analyze tool\ntype AnalyzeRequest struct {\n\tNamespace       string   `json:\"namespace,omitempty\"`\n\tBackend         string   `json:\"backend,omitempty\"`\n\tLanguage        string   `json:\"language,omitempty\"`\n\tFilters         []string `json:\"filters,omitempty\"`\n\tLabelSelector   string   `json:\"labelSelector,omitempty\"`\n\tNoCache         bool     `json:\"noCache,omitempty\"`\n\tExplain         bool     `json:\"explain,omitempty\"`\n\tMaxConcurrency  int      `json:\"maxConcurrency,omitempty\"`\n\tWithDoc         bool     `json:\"withDoc,omitempty\"`\n\tInteractiveMode bool     `json:\"interactiveMode,omitempty\"`\n\tCustomHeaders   []string `json:\"customHeaders,omitempty\"`\n\tWithStats       bool     `json:\"withStats,omitempty\"`\n\tAnonymize       bool     `json:\"anonymize,omitempty\"`\n}\n\n// AnalyzeResponse represents the output of the analyze tool\ntype AnalyzeResponse struct {\n\tResults string `json:\"results\"`\n}\n\n// ClusterInfoRequest represents the input parameters for the cluster-info tool\ntype ClusterInfoRequest struct {\n\t// Empty struct as we don't need any input parameters\n}\n\n// ClusterInfoResponse represents the output of the cluster-info tool\ntype ClusterInfoResponse struct {\n\tInfo string `json:\"info\"`\n}\n\n// ConfigRequest represents the input parameters for the config tool\ntype ConfigRequest struct {\n\tCustomAnalyzers []struct {\n\t\tName       string `json:\"name\"`\n\t\tConnection struct {\n\t\t\tUrl  string `json:\"url\"`\n\t\t\tPort int    `json:\"port\"`\n\t\t} `json:\"connection\"`\n\t} `json:\"customAnalyzers,omitempty\"`\n\tCache struct {\n\t\tType string `json:\"type\"`\n\t\t// S3 specific fields\n\t\tBucketName string `json:\"bucketName,omitempty\"`\n\t\tRegion     string `json:\"region,omitempty\"`\n\t\tEndpoint   string `json:\"endpoint,omitempty\"`\n\t\tInsecure   bool   `json:\"insecure,omitempty\"`\n\t\t// Azure specific fields\n\t\tStorageAccount string `json:\"storageAccount,omitempty\"`\n\t\tContainerName  string `json:\"containerName,omitempty\"`\n\t\t// GCS specific fields\n\t\tProjectId string `json:\"projectId,omitempty\"`\n\t} `json:\"cache,omitempty\"`\n}\n\n// ConfigResponse represents the output of the config tool\ntype ConfigResponse struct {\n\tStatus string `json:\"status\"`\n}\n\n// handleAnalyze handles the analyze tool\nfunc (s *K8sGptMCPServer) handleAnalyze(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\n\tvar req AnalyzeRequest\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif req.Backend == \"\" {\n\t\tif s.aiProvider.Name != \"\" {\n\t\t\treq.Backend = s.aiProvider.Name\n\t\t} else {\n\t\t\treq.Backend = \"openai\" // fallback default\n\t\t}\n\t}\n\n\t// Get stored filters if not specified\n\tif len(req.Filters) == 0 {\n\t\treq.Filters = viper.GetStringSlice(\"active_filters\")\n\t}\n\n\t// Validate MaxConcurrency to prevent excessive memory allocation\n\treq.MaxConcurrency = validateMaxConcurrency(req.MaxConcurrency)\n\n\t// Create a new analysis with the request parameters\n\tanalysis, err := analysis.NewAnalysis(\n\t\treq.Backend,\n\t\treq.Language,\n\t\treq.Filters,\n\t\treq.Namespace,\n\t\treq.LabelSelector,\n\t\treq.NoCache,\n\t\treq.Explain,\n\t\treq.MaxConcurrency,\n\t\treq.WithDoc,\n\t\treq.InteractiveMode,\n\t\treq.CustomHeaders,\n\t\treq.WithStats,\n\t)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create analysis: %v\", err), nil\n\t}\n\tdefer analysis.Close()\n\n\t// Run the analysis\n\tanalysis.RunAnalysis()\n\tif req.Explain {\n\n\t\tvar output string\n\t\terr := analysis.GetAIResults(output, req.Anonymize)\n\t\tif err != nil {\n\t\t\treturn mcp.NewToolResultErrorf(\"Failed to get results from AI: %v\", err), nil\n\t\t}\n\n\t\t// Convert results to JSON string using PrintOutput\n\t\toutputBytes, err := analysis.PrintOutput(\"text\")\n\t\tif err != nil {\n\t\t\treturn mcp.NewToolResultErrorf(\"Failed to convert results to string: %v\", err), nil\n\t\t}\n\t\tplainText := stripANSI(string(outputBytes))\n\t\treturn mcp.NewToolResultText(plainText), nil\n\t} else {\n\t\t// Get the output\n\t\toutput, err := analysis.PrintOutput(\"json\")\n\t\tif err != nil {\n\t\t\treturn mcp.NewToolResultErrorf(\"Failed to print output: %v\", err), nil\n\t\t}\n\t\treturn mcp.NewToolResultText(string(output)), nil\n\t}\n}\n\n// validateMaxConcurrency validates and bounds the MaxConcurrency parameter\nfunc validateMaxConcurrency(maxConcurrency int) int {\n\tconst maxAllowedConcurrency = 100\n\tif maxConcurrency <= 0 {\n\t\treturn 10 // Default value if not set\n\t} else if maxConcurrency > maxAllowedConcurrency {\n\t\treturn maxAllowedConcurrency // Cap at a reasonable maximum\n\t}\n\treturn maxConcurrency\n}\n\n// handleClusterInfo handles the cluster-info tool\nfunc (s *K8sGptMCPServer) handleClusterInfo(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// Create a new Kubernetes client\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\t// Get cluster info from the client\n\tversion, err := client.Client.Discovery().ServerVersion()\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"failed to get cluster version: %v\", err), nil\n\t}\n\n\tinfo := fmt.Sprintf(\"Kubernetes %s\", version.GitVersion)\n\treturn mcp.NewToolResultText(info), nil\n}\n\n// handleConfig handles the config tool\nfunc (s *K8sGptMCPServer) handleConfig(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\t// Parse request arguments\n\tvar req ConfigRequest\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\t// Create a new config handler\n\thandler := &config.Handler{}\n\n\t// Convert request to AddConfigRequest\n\taddConfigReq := &schemav1.AddConfigRequest{\n\t\tCustomAnalyzers: make([]*schemav1.CustomAnalyzer, 0),\n\t}\n\n\t// Add custom analyzers if present\n\tif len(req.CustomAnalyzers) > 0 {\n\t\tfor _, ca := range req.CustomAnalyzers {\n\t\t\taddConfigReq.CustomAnalyzers = append(addConfigReq.CustomAnalyzers, &schemav1.CustomAnalyzer{\n\t\t\t\tName: ca.Name,\n\t\t\t\tConnection: &schemav1.Connection{\n\t\t\t\t\tUrl:  ca.Connection.Url,\n\t\t\t\t\tPort: fmt.Sprintf(\"%d\", ca.Connection.Port),\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\t// Add cache configuration if present\n\tif req.Cache.Type != \"\" {\n\t\tcacheConfig := &schemav1.Cache{}\n\t\tswitch req.Cache.Type {\n\t\tcase \"s3\":\n\t\t\tcacheConfig.CacheType = &schemav1.Cache_S3Cache{\n\t\t\t\tS3Cache: &schemav1.S3Cache{\n\t\t\t\t\tBucketName: req.Cache.BucketName,\n\t\t\t\t\tRegion:     req.Cache.Region,\n\t\t\t\t\tEndpoint:   req.Cache.Endpoint,\n\t\t\t\t\tInsecure:   req.Cache.Insecure,\n\t\t\t\t},\n\t\t\t}\n\t\tcase \"azure\":\n\t\t\tcacheConfig.CacheType = &schemav1.Cache_AzureCache{\n\t\t\t\tAzureCache: &schemav1.AzureCache{\n\t\t\t\t\tStorageAccount: req.Cache.StorageAccount,\n\t\t\t\t\tContainerName:  req.Cache.ContainerName,\n\t\t\t\t},\n\t\t\t}\n\t\tcase \"gcs\":\n\t\t\tcacheConfig.CacheType = &schemav1.Cache_GcsCache{\n\t\t\t\tGcsCache: &schemav1.GCSCache{\n\t\t\t\t\tBucketName: req.Cache.BucketName,\n\t\t\t\t\tRegion:     req.Cache.Region,\n\t\t\t\t\tProjectId:  req.Cache.ProjectId,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t\taddConfigReq.Cache = cacheConfig\n\t}\n\n\t// Apply the configuration using the shared function\n\tif err := handler.ApplyConfig(ctx, addConfigReq); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to add config: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(\"Successfully added configuration\"), nil\n}\n\n// registerPrompts registers the prompts for the MCP server\nfunc (s *K8sGptMCPServer) registerPrompts() error {\n\t// Register troubleshooting prompts\n\tpodTroubleshootPrompt := mcp.NewPrompt(\"troubleshoot-pod\",\n\t\tmcp.WithPromptDescription(\"Guide for troubleshooting pod issues in Kubernetes\"),\n\t\tmcp.WithArgument(\"podName\"),\n\t\tmcp.WithArgument(\"namespace\"),\n\t)\n\ts.server.AddPrompt(podTroubleshootPrompt, s.getTroubleshootPodPrompt)\n\n\tdeploymentTroubleshootPrompt := mcp.NewPrompt(\"troubleshoot-deployment\",\n\t\tmcp.WithPromptDescription(\"Guide for troubleshooting deployment issues in Kubernetes\"),\n\t\tmcp.WithArgument(\"deploymentName\"),\n\t\tmcp.WithArgument(\"namespace\"),\n\t)\n\ts.server.AddPrompt(deploymentTroubleshootPrompt, s.getTroubleshootDeploymentPrompt)\n\n\tgeneralTroubleshootPrompt := mcp.NewPrompt(\"troubleshoot-cluster\",\n\t\tmcp.WithPromptDescription(\"General guide for troubleshooting Kubernetes cluster issues\"),\n\t)\n\ts.server.AddPrompt(generalTroubleshootPrompt, s.getTroubleshootClusterPrompt)\n\n\treturn nil\n}\n\n// registerResources registers the resources for the MCP server\nfunc (s *K8sGptMCPServer) registerResources() error {\n\tclusterInfoResource := mcp.NewResource(\"cluster-info\", \"cluster-info\",\n\t\tmcp.WithResourceDescription(\"Get information about the Kubernetes cluster\"),\n\t\tmcp.WithMIMEType(\"application/json\"),\n\t)\n\ts.server.AddResource(clusterInfoResource, s.getClusterInfo)\n\n\tnamespacesResource := mcp.NewResource(\"namespaces\", \"namespaces\",\n\t\tmcp.WithResourceDescription(\"List all namespaces in the cluster\"),\n\t\tmcp.WithMIMEType(\"application/json\"),\n\t)\n\ts.server.AddResource(namespacesResource, s.getNamespacesResource)\n\n\tactiveFiltersResource := mcp.NewResource(\"active-filters\", \"active-filters\",\n\t\tmcp.WithResourceDescription(\"Get currently active analyzers/filters\"),\n\t\tmcp.WithMIMEType(\"application/json\"),\n\t)\n\ts.server.AddResource(activeFiltersResource, s.getActiveFiltersResource)\n\n\treturn nil\n}\n\nfunc (s *K8sGptMCPServer) getClusterInfo(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {\n\t// Create a new Kubernetes client\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Kubernetes client: %v\", err)\n\t}\n\n\t// Get cluster info from the client\n\tversion, err := client.Client.Discovery().ServerVersion()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get cluster version: %v\", err)\n\t}\n\n\tdata, err := json.Marshal(map[string]string{\n\t\t\"version\":    version.String(),\n\t\t\"platform\":   version.Platform,\n\t\t\"gitVersion\": version.GitVersion,\n\t})\n\tif err != nil {\n\t\treturn []mcp.ResourceContents{\n\t\t\t&mcp.TextResourceContents{\n\t\t\t\tURI:      \"cluster-info\",\n\t\t\t\tMIMEType: \"text/plain\",\n\t\t\t\tText:     \"Failed to marshal cluster info\",\n\t\t\t},\n\t\t}, nil\n\t}\n\n\treturn []mcp.ResourceContents{\n\t\t&mcp.TextResourceContents{\n\t\t\tURI:      \"cluster-info\",\n\t\t\tMIMEType: \"application/json\",\n\t\t\tText:     string(data),\n\t\t},\n\t}, nil\n}\n\nfunc (s *K8sGptMCPServer) getNamespacesResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create Kubernetes client: %v\", err)\n\t}\n\n\tnamespaces, err := client.Client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list namespaces: %v\", err)\n\t}\n\n\t// Extract just the namespace names\n\tnames := make([]string, 0, len(namespaces.Items))\n\tfor _, ns := range namespaces.Items {\n\t\tnames = append(names, ns.Name)\n\t}\n\n\tdata, err := json.Marshal(map[string]interface{}{\n\t\t\"count\":      len(names),\n\t\t\"namespaces\": names,\n\t})\n\tif err != nil {\n\t\treturn []mcp.ResourceContents{\n\t\t\t&mcp.TextResourceContents{\n\t\t\t\tURI:      \"namespaces\",\n\t\t\t\tMIMEType: \"text/plain\",\n\t\t\t\tText:     \"Failed to marshal namespaces\",\n\t\t\t},\n\t\t}, nil\n\t}\n\n\treturn []mcp.ResourceContents{\n\t\t&mcp.TextResourceContents{\n\t\t\tURI:      \"namespaces\",\n\t\t\tMIMEType: \"application/json\",\n\t\t\tText:     string(data),\n\t\t},\n\t}, nil\n}\n\nfunc (s *K8sGptMCPServer) getActiveFiltersResource(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\n\tdata, err := json.Marshal(map[string]interface{}{\n\t\t\"activeFilters\": activeFilters,\n\t\t\"count\":         len(activeFilters),\n\t})\n\tif err != nil {\n\t\treturn []mcp.ResourceContents{\n\t\t\t&mcp.TextResourceContents{\n\t\t\t\tURI:      \"active-filters\",\n\t\t\t\tMIMEType: \"text/plain\",\n\t\t\t\tText:     \"Failed to marshal active filters\",\n\t\t\t},\n\t\t}, nil\n\t}\n\n\treturn []mcp.ResourceContents{\n\t\t&mcp.TextResourceContents{\n\t\t\tURI:      \"active-filters\",\n\t\t\tMIMEType: \"application/json\",\n\t\t\tText:     string(data),\n\t\t},\n\t}, nil\n}\n\n// Close closes the MCP server and releases resources\nfunc (s *K8sGptMCPServer) Close() error {\n\treturn nil\n}\n\n// zapLoggerAdapter adapts zap.Logger to the interface expected by mark3labs/mcp-go\ntype zapLoggerAdapter struct {\n\tlogger *zap.Logger\n}\n\nfunc (z *zapLoggerAdapter) Infof(format string, v ...any) {\n\tz.logger.Info(fmt.Sprintf(format, v...))\n}\n\nfunc (z *zapLoggerAdapter) Errorf(format string, v ...any) {\n\tz.logger.Error(fmt.Sprintf(format, v...))\n}\n\n// stripANSI removes ANSI escape sequences from a string\nfunc stripANSI(input string) string {\n\tre := regexp.MustCompile(`\\x1b\\[[0-9;]*m`)\n\treturn re.ReplaceAllString(input, \"\")\n}\n"
  },
  {
    "path": "pkg/server/mcp_handlers.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/integration\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/mark3labs/mcp-go/mcp\"\n\t\"github.com/spf13/viper\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst (\n\t// DefaultListLimit is the default maximum number of resources to return\n\tDefaultListLimit = 100\n\t// MaxListLimit is the maximum allowed limit for list operations\n\tMaxListLimit = 1000\n)\n\n// resourceLister defines a function that lists Kubernetes resources\ntype resourceLister func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error)\n\n// resourceGetter defines a function that gets a single Kubernetes resource\ntype resourceGetter func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error)\n\n// resourceRegistry maps resource types to their list and get functions\nvar resourceRegistry = map[string]struct {\n\tlist resourceLister\n\tget  resourceGetter\n}{\n\t\"pod\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Pods(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"deployment\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().Deployments(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"service\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Services(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"node\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Nodes().List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"job\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.BatchV1().Jobs(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"cronjob\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.BatchV1().CronJobs(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.BatchV1().CronJobs(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"statefulset\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().StatefulSets(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"daemonset\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().DaemonSets(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"replicaset\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().ReplicaSets(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.AppsV1().ReplicaSets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"configmap\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().ConfigMaps(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"secret\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Secrets(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"ingress\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.NetworkingV1().Ingresses(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.NetworkingV1().Ingresses(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"persistentvolumeclaim\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n\t\"persistentvolume\": {\n\t\tlist: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().PersistentVolumes().List(ctx, opts)\n\t\t},\n\t\tget: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {\n\t\t\treturn client.Client.CoreV1().PersistentVolumes().Get(ctx, name, metav1.GetOptions{})\n\t\t},\n\t},\n}\n\n// Resource type aliases for convenience\nvar resourceTypeAliases = map[string]string{\n\t\"pods\":                      \"pod\",\n\t\"deployments\":               \"deployment\",\n\t\"services\":                  \"service\",\n\t\"svc\":                       \"service\",\n\t\"nodes\":                     \"node\",\n\t\"jobs\":                      \"job\",\n\t\"cronjobs\":                  \"cronjob\",\n\t\"statefulsets\":              \"statefulset\",\n\t\"sts\":                       \"statefulset\",\n\t\"daemonsets\":                \"daemonset\",\n\t\"ds\":                        \"daemonset\",\n\t\"replicasets\":               \"replicaset\",\n\t\"rs\":                        \"replicaset\",\n\t\"configmaps\":                \"configmap\",\n\t\"cm\":                        \"configmap\",\n\t\"secrets\":                   \"secret\",\n\t\"ingresses\":                 \"ingress\",\n\t\"ing\":                       \"ingress\",\n\t\"persistentvolumeclaims\":    \"persistentvolumeclaim\",\n\t\"pvc\":                       \"persistentvolumeclaim\",\n\t\"persistentvolumes\":         \"persistentvolume\",\n\t\"pv\":                        \"persistentvolume\",\n}\n\n// normalizeResourceType converts resource type variants to canonical form\nfunc normalizeResourceType(resourceType string) (string, error) {\n\tnormalized := strings.ToLower(resourceType)\n\t\n\t// Check if it's an alias\n\tif canonical, ok := resourceTypeAliases[normalized]; ok {\n\t\tnormalized = canonical\n\t}\n\t\n\t// Check if it's a known resource type\n\tif _, ok := resourceRegistry[normalized]; !ok {\n\t\treturn \"\", fmt.Errorf(\"unsupported resource type: %s\", resourceType)\n\t}\n\t\n\treturn normalized, nil\n}\n\n// marshalJSON marshals data to JSON with proper error handling\nfunc marshalJSON(data interface{}) (string, error) {\n\tjsonData, err := json.MarshalIndent(data, \"\", \"  \")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal JSON: %w\", err)\n\t}\n\treturn string(jsonData), nil\n}\n\n// handleListResources lists Kubernetes resources of a specific type\nfunc (s *K8sGptMCPServer) handleListResources(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tResourceType  string `json:\"resourceType\"`\n\t\tNamespace     string `json:\"namespace,omitempty\"`\n\t\tLabelSelector string `json:\"labelSelector,omitempty\"`\n\t\tLimit         int64  `json:\"limit,omitempty\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif req.ResourceType == \"\" {\n\t\treturn mcp.NewToolResultErrorf(\"resourceType is required\"), nil\n\t}\n\n\t// Normalize and validate resource type\n\tresourceType, err := normalizeResourceType(req.ResourceType)\n\tif err != nil {\n\t\tsupportedTypes := make([]string, 0, len(resourceRegistry))\n\t\tfor key := range resourceRegistry {\n\t\t\tsupportedTypes = append(supportedTypes, key)\n\t\t}\n\t\treturn mcp.NewToolResultErrorf(\"%v. Supported types: %v\", err, supportedTypes), nil\n\t}\n\n\t// Set default and validate limit\n\tif req.Limit == 0 {\n\t\treq.Limit = DefaultListLimit\n\t} else if req.Limit > MaxListLimit {\n\t\treq.Limit = MaxListLimit\n\t}\n\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\tlistOptions := metav1.ListOptions{\n\t\tLabelSelector: req.LabelSelector,\n\t\tLimit:         req.Limit,\n\t}\n\n\t// Get the list function from registry\n\tlistFunc := resourceRegistry[resourceType].list\n\tresult, err := listFunc(ctx, client, req.Namespace, listOptions)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to list %s: %v\", resourceType, err), nil\n\t}\n\n\t// Extract items from the result (all list types have an Items field)\n\tresultJSON, err := marshalJSON(result)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// handleGetResource gets detailed information about a specific resource\nfunc (s *K8sGptMCPServer) handleGetResource(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tResourceType string `json:\"resourceType\"`\n\t\tName         string `json:\"name\"`\n\t\tNamespace    string `json:\"namespace,omitempty\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif req.ResourceType == \"\" {\n\t\treturn mcp.NewToolResultErrorf(\"resourceType is required\"), nil\n\t}\n\tif req.Name == \"\" {\n\t\treturn mcp.NewToolResultErrorf(\"name is required\"), nil\n\t}\n\n\t// Normalize and validate resource type\n\tresourceType, err := normalizeResourceType(req.ResourceType)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"%v\", err), nil\n\t}\n\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\t// Get the get function from registry\n\tgetFunc := resourceRegistry[resourceType].get\n\tresult, err := getFunc(ctx, client, req.Namespace, req.Name)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to get %s '%s': %v\", resourceType, req.Name, err), nil\n\t}\n\n\tresultJSON, err := marshalJSON(result)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// handleListNamespaces lists all namespaces in the cluster\nfunc (s *K8sGptMCPServer) handleListNamespaces(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\tnamespaces, err := client.Client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to list namespaces: %v\", err), nil\n\t}\n\n\tresultJSON, err := marshalJSON(namespaces.Items)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// handleListEvents lists Kubernetes events\nfunc (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tNamespace          string `json:\"namespace,omitempty\"`\n\t\tInvolvedObjectName string `json:\"involvedObjectName,omitempty\"`\n\t\tInvolvedObjectKind string `json:\"involvedObjectKind,omitempty\"`\n\t\tLimit              int64  `json:\"limit,omitempty\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif req.Limit == 0 {\n\t\treq.Limit = DefaultListLimit\n\t} else if req.Limit > MaxListLimit {\n\t\treq.Limit = MaxListLimit\n\t}\n\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\tlistOptions := metav1.ListOptions{\n\t\tLimit: req.Limit,\n\t}\n\n\tevents, err := client.Client.CoreV1().Events(req.Namespace).List(ctx, listOptions)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to list events: %v\", err), nil\n\t}\n\n\t// Filter events if needed\n\tfilteredEvents := []corev1.Event{}\n\tfor _, event := range events.Items {\n\t\tif req.InvolvedObjectName != \"\" && event.InvolvedObject.Name != req.InvolvedObjectName {\n\t\t\tcontinue\n\t\t}\n\t\tif req.InvolvedObjectKind != \"\" && event.InvolvedObject.Kind != req.InvolvedObjectKind {\n\t\t\tcontinue\n\t\t}\n\t\tfilteredEvents = append(filteredEvents, event)\n\t}\n\n\tresultJSON, err := marshalJSON(filteredEvents)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// handleGetLogs retrieves logs from a pod container\nfunc (s *K8sGptMCPServer) handleGetLogs(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tPodName      string `json:\"podName\"`\n\t\tNamespace    string `json:\"namespace\"`\n\t\tContainer    string `json:\"container,omitempty\"`\n\t\tPrevious     bool   `json:\"previous,omitempty\"`\n\t\tTailLines    int64  `json:\"tailLines,omitempty\"`\n\t\tSinceSeconds int64  `json:\"sinceSeconds,omitempty\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif req.PodName == \"\" {\n\t\treturn mcp.NewToolResultErrorf(\"podName is required\"), nil\n\t}\n\tif req.Namespace == \"\" {\n\t\treturn mcp.NewToolResultErrorf(\"namespace is required\"), nil\n\t}\n\n\tif req.TailLines == 0 {\n\t\treq.TailLines = 100\n\t}\n\n\tclient, err := kubernetes.NewClient(\"\", \"\")\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to create Kubernetes client: %v\", err), nil\n\t}\n\n\tpodLogOpts := &corev1.PodLogOptions{\n\t\tContainer: req.Container,\n\t\tPrevious:  req.Previous,\n\t\tTailLines: &req.TailLines,\n\t}\n\n\tif req.SinceSeconds > 0 {\n\t\tpodLogOpts.SinceSeconds = &req.SinceSeconds\n\t}\n\n\tlogRequest := client.Client.CoreV1().Pods(req.Namespace).GetLogs(req.PodName, podLogOpts)\n\tlogStream, err := logRequest.Stream(ctx)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to get logs: %v\", err), nil\n\t}\n\tdefer func() {\n\t\t_ = logStream.Close()\n\t}()\n\n\tlogs, err := io.ReadAll(logStream)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to read logs: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(string(logs)), nil\n}\n\n// handleListFilters lists available and active filters\nfunc (s *K8sGptMCPServer) handleListFilters(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tcoreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()\n\tactive := viper.GetStringSlice(\"active_filters\")\n\n\tresult := map[string]interface{}{\n\t\t\"coreFilters\":        coreFilters,\n\t\t\"additionalFilters\":  additionalFilters,\n\t\t\"integrationFilters\": integrationFilters,\n\t\t\"activeFilters\":      active,\n\t}\n\n\tresultJSON, err := marshalJSON(result)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// handleAddFilters adds filters to enable specific analyzers\nfunc (s *K8sGptMCPServer) handleAddFilters(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tFilters []string `json:\"filters\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif len(req.Filters) == 0 {\n\t\treturn mcp.NewToolResultErrorf(\"filters array is required and cannot be empty\"), nil\n\t}\n\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\taddedFilters := []string{}\n\t\n\tfor _, filter := range req.Filters {\n\t\tif !contains(activeFilters, filter) {\n\t\t\tactiveFilters = append(activeFilters, filter)\n\t\t\taddedFilters = append(addedFilters, filter)\n\t\t}\n\t}\n\n\tviper.Set(\"active_filters\", activeFilters)\n\tif err := viper.WriteConfig(); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to save configuration: %v\", err), nil\n\t}\n\n\tif len(addedFilters) == 0 {\n\t\treturn mcp.NewToolResultText(\"All specified filters were already active\"), nil\n\t}\n\n\treturn mcp.NewToolResultText(fmt.Sprintf(\"Successfully added filters: %v\", addedFilters)), nil\n}\n\n// handleRemoveFilters removes filters to disable specific analyzers\nfunc (s *K8sGptMCPServer) handleRemoveFilters(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tvar req struct {\n\t\tFilters []string `json:\"filters\"`\n\t}\n\tif err := request.BindArguments(&req); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to parse request arguments: %v\", err), nil\n\t}\n\n\tif len(req.Filters) == 0 {\n\t\treturn mcp.NewToolResultErrorf(\"filters array is required and cannot be empty\"), nil\n\t}\n\n\tactiveFilters := viper.GetStringSlice(\"active_filters\")\n\tnewFilters := []string{}\n\tremovedFilters := []string{}\n\t\n\tfor _, filter := range activeFilters {\n\t\tif !contains(req.Filters, filter) {\n\t\t\tnewFilters = append(newFilters, filter)\n\t\t} else {\n\t\t\tremovedFilters = append(removedFilters, filter)\n\t\t}\n\t}\n\n\tviper.Set(\"active_filters\", newFilters)\n\tif err := viper.WriteConfig(); err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to save configuration: %v\", err), nil\n\t}\n\n\tif len(removedFilters) == 0 {\n\t\treturn mcp.NewToolResultText(\"None of the specified filters were active\"), nil\n\t}\n\n\treturn mcp.NewToolResultText(fmt.Sprintf(\"Successfully removed filters: %v\", removedFilters)), nil\n}\n\n// handleListIntegrations lists available integrations\nfunc (s *K8sGptMCPServer) handleListIntegrations(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {\n\tintegrationProvider := integration.NewIntegration()\n\tintegrations := integrationProvider.List()\n\n\tresult := []map[string]interface{}{}\n\tfor _, integ := range integrations {\n\t\tactive, _ := integrationProvider.IsActivate(integ)\n\t\tresult = append(result, map[string]interface{}{\n\t\t\t\"name\":   integ,\n\t\t\t\"active\": active,\n\t\t})\n\t}\n\n\tresultJSON, err := marshalJSON(result)\n\tif err != nil {\n\t\treturn mcp.NewToolResultErrorf(\"Failed to serialize result: %v\", err), nil\n\t}\n\n\treturn mcp.NewToolResultText(resultJSON), nil\n}\n\n// contains checks if a string slice contains a specific string\nfunc contains(slice []string, item string) bool {\n\tfor _, s := range slice {\n\t\tif s == item {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/server/mcp_prompts.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/mark3labs/mcp-go/mcp\"\n)\n\n// getTroubleshootPodPrompt returns a prompt for pod troubleshooting\nfunc (s *K8sGptMCPServer) getTroubleshootPodPrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {\n\tpodName := \"\"\n\tnamespace := \"\"\n\tif request.Params.Arguments != nil {\n\t\tpodName = request.Params.Arguments[\"podName\"]\n\t\tnamespace = request.Params.Arguments[\"namespace\"]\n\t}\n\n\tpromptText := fmt.Sprintf(`You are troubleshooting a Kubernetes pod issue.\n\nPod: %s\nNamespace: %s\n\nTroubleshooting steps:\n1. Use 'get-resource' tool to get pod details and check status, conditions, and events\n2. Use 'list-events' tool with the pod name to see recent events\n3. Use 'get-logs' tool to check container logs for errors\n4. Check if the pod has multiple containers and inspect each\n5. If the pod is in CrashLoopBackOff, use 'get-logs' with previous=true\n6. Use 'analyze' tool with filters=['Pod'] to get AI-powered analysis\n7. Check related resources like ConfigMaps, Secrets, and PVCs\n\nCommon issues to check:\n- Image pull errors (check imagePullSecrets)\n- Resource limits (CPU/memory)\n- Liveness/readiness probe failures\n- Volume mount issues\n- Environment variable problems\n- Network connectivity issues`, podName, namespace)\n\n\treturn &mcp.GetPromptResult{\n\t\tDescription: \"Pod troubleshooting guide\",\n\t\tMessages: []mcp.PromptMessage{\n\t\t\t{\n\t\t\t\tRole: \"user\",\n\t\t\t\tContent: mcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: promptText,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// getTroubleshootDeploymentPrompt returns a prompt for deployment troubleshooting\nfunc (s *K8sGptMCPServer) getTroubleshootDeploymentPrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {\n\tdeploymentName := \"\"\n\tnamespace := \"\"\n\tif request.Params.Arguments != nil {\n\t\tdeploymentName = request.Params.Arguments[\"deploymentName\"]\n\t\tnamespace = request.Params.Arguments[\"namespace\"]\n\t}\n\n\tpromptText := fmt.Sprintf(`You are troubleshooting a Kubernetes deployment issue.\n\nDeployment: %s\nNamespace: %s\n\nTroubleshooting steps:\n1. Use 'get-resource' tool to get deployment details and check replica status\n2. Use 'list-resources' with resourceType='replicasets' to check ReplicaSets\n3. Use 'list-resources' with resourceType='pods' and labelSelector to find deployment pods\n4. Use 'list-events' tool to see deployment-related events\n5. Use 'analyze' tool with filters=['Deployment','Pod'] for comprehensive analysis\n6. Check pod status and logs for individual pod issues\n7. Verify image availability and pull secrets\n8. Check resource quotas and limits\n\nCommon deployment issues:\n- Insufficient resources in the cluster\n- Image pull failures\n- Invalid configuration (ConfigMaps/Secrets)\n- Failed rolling updates\n- Readiness probe failures preventing rollout\n- PVC binding issues\n- Node selector/affinity constraints`, deploymentName, namespace)\n\n\treturn &mcp.GetPromptResult{\n\t\tDescription: \"Deployment troubleshooting guide\",\n\t\tMessages: []mcp.PromptMessage{\n\t\t\t{\n\t\t\t\tRole: \"user\",\n\t\t\t\tContent: mcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: promptText,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n\n// getTroubleshootClusterPrompt returns a prompt for general cluster troubleshooting\nfunc (s *K8sGptMCPServer) getTroubleshootClusterPrompt(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {\n\tpromptText := `You are performing a general Kubernetes cluster health check and troubleshooting.\n\nRecommended troubleshooting workflow:\n\n1. CLUSTER OVERVIEW:\n   - Use 'cluster-info' to get cluster version\n   - Use 'list-namespaces' to see all namespaces\n   - Use 'list-resources' with resourceType='nodes' to check node health\n\n2. RESOURCE ANALYSIS:\n   - Use 'analyze' tool with explain=true for comprehensive AI-powered analysis\n   - Start with core resources: filters=['Pod','Deployment','Service']\n   - Add more filters as needed: ['Node','PersistentVolumeClaim','Job','CronJob']\n\n3. EVENT INSPECTION:\n   - Use 'list-events' to see recent cluster events\n   - Filter by namespace for focused troubleshooting\n   - Look for Warning and Error events\n\n4. SPECIFIC RESOURCE INVESTIGATION:\n   - Use 'list-resources' to find problematic resources\n   - Use 'get-resource' for detailed inspection\n   - Use 'get-logs' to examine container logs\n\n5. CONFIGURATION CHECK:\n   - Use 'list-filters' to see available analyzers\n   - Use 'list-integrations' to check integrations (Prometheus, AWS, etc.)\n   - Use 'config' tool to modify settings if needed\n\nCommon cluster-wide issues:\n- Node pressure (CPU, memory, disk)\n- Network policies blocking traffic\n- Storage provisioning problems\n- RBAC permission issues\n- Certificate expiration\n- Control plane component failures\n- Resource quota exhaustion\n- DNS resolution problems\n\nUse the available tools systematically to narrow down the issue.`\n\n\treturn &mcp.GetPromptResult{\n\t\tDescription: \"General cluster troubleshooting guide\",\n\t\tMessages: []mcp.PromptMessage{\n\t\t\t{\n\t\t\t\tRole: \"user\",\n\t\t\t\tContent: mcp.TextContent{\n\t\t\t\t\tType: \"text\",\n\t\t\t\t\tText: promptText,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/server/query/handler.go",
    "content": "package query\n\nimport rpc \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc\"\n\ntype Handler struct {\n\trpc.UnimplementedServerQueryServiceServer\n}\n"
  },
  {
    "path": "pkg/server/query/query.go",
    "content": "package query\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n)\n\nfunc (h *Handler) Query(ctx context.Context, i *schemav1.QueryRequest) (\n\t*schemav1.QueryResponse,\n\terror,\n) {\n\t// Create client factory and config provider\n\tfactory := ai.GetAIClientFactory()\n\tconfigProvider := ai.GetConfigProvider()\n\n\t// Use the factory to create the client\n\taiClient := factory.NewClient(i.Backend)\n\tdefer aiClient.Close()\n\n\tvar configAI ai.AIConfiguration\n\tif err := configProvider.UnmarshalKey(\"ai\", &configAI); err != nil {\n\t\treturn &schemav1.QueryResponse{\n\t\t\tResponse: \"\",\n\t\t\tError: &schemav1.QueryError{\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to unmarshal AI configuration: %v\", err),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\tvar aiProvider ai.AIProvider\n\tfor _, provider := range configAI.Providers {\n\t\tif i.Backend == provider.Name {\n\t\t\taiProvider = provider\n\t\t\tbreak\n\t\t}\n\t}\n\tif aiProvider.Name == \"\" {\n\t\treturn &schemav1.QueryResponse{\n\t\t\tResponse: \"\",\n\t\t\tError: &schemav1.QueryError{\n\t\t\t\tMessage: fmt.Sprintf(\"AI provider %s not found in configuration\", i.Backend),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\t// Configure the AI client\n\tif err := aiClient.Configure(&aiProvider); err != nil {\n\t\treturn &schemav1.QueryResponse{\n\t\t\tResponse: \"\",\n\t\t\tError: &schemav1.QueryError{\n\t\t\t\tMessage: fmt.Sprintf(\"Failed to configure AI client: %v\", err),\n\t\t\t},\n\t\t}, nil\n\t}\n\n\tresp, err := aiClient.GetCompletion(ctx, i.Query)\n\tvar errMessage string = \"\"\n\tif err != nil {\n\t\terrMessage = err.Error()\n\t}\n\treturn &schemav1.QueryResponse{\n\t\tResponse: resp,\n\t\tError: &schemav1.QueryError{\n\t\t\tMessage: errMessage,\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/server/query/query_test.go",
    "content": "package query\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\tschemav1 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n)\n\n// MockAI is a mock implementation of the ai.IAI interface for testing\ntype MockAI struct {\n\tmock.Mock\n}\n\nfunc (m *MockAI) Configure(config ai.IAIConfig) error {\n\targs := m.Called(config)\n\treturn args.Error(0)\n}\n\nfunc (m *MockAI) GetCompletion(ctx context.Context, prompt string) (string, error) {\n\targs := m.Called(ctx, prompt)\n\treturn args.String(0), args.Error(1)\n}\n\nfunc (m *MockAI) GetName() string {\n\targs := m.Called()\n\treturn args.String(0)\n}\n\nfunc (m *MockAI) Close() {\n\tm.Called()\n}\n\n// MockAIClientFactory is a mock implementation of AIClientFactory\ntype MockAIClientFactory struct {\n\tmock.Mock\n}\n\nfunc (m *MockAIClientFactory) NewClient(provider string) ai.IAI {\n\targs := m.Called(provider)\n\treturn args.Get(0).(ai.IAI)\n}\n\n// MockConfigProvider is a mock implementation of ConfigProvider\ntype MockConfigProvider struct {\n\tmock.Mock\n}\n\nfunc (m *MockConfigProvider) UnmarshalKey(key string, rawVal interface{}) error {\n\targs := m.Called(key, rawVal)\n\n\t// If we want to set the rawVal (which is a pointer)\n\tif fn, ok := args.Get(0).(func(interface{})); ok && fn != nil {\n\t\tfn(rawVal)\n\t}\n\n\t// Return the error as the first return value\n\treturn args.Error(0)\n}\n\nfunc TestQuery_Success(t *testing.T) {\n\t// Setup mocks\n\tmockAI := new(MockAI)\n\tmockFactory := new(MockAIClientFactory)\n\tmockConfig := new(MockConfigProvider)\n\n\t// Set test implementations\n\tai.SetTestAIClientFactory(mockFactory)\n\tai.SetTestConfigProvider(mockConfig)\n\tdefer ai.ResetTestImplementations()\n\n\t// Define test data\n\ttestBackend := \"test-backend\"\n\ttestQuery := \"test query\"\n\ttestResponse := \"test response\"\n\n\t// Setup expectations\n\tmockFactory.On(\"NewClient\", testBackend).Return(mockAI)\n\tmockAI.On(\"Close\").Return()\n\n\t// Set up configuration with a valid provider\n\tmockConfig.On(\"UnmarshalKey\", \"ai\", mock.Anything).Run(func(args mock.Arguments) {\n\t\tconfig := args.Get(1).(*ai.AIConfiguration)\n\t\t*config = ai.AIConfiguration{\n\t\t\tProviders: []ai.AIProvider{\n\t\t\t\t{\n\t\t\t\t\tName:     testBackend,\n\t\t\t\t\tPassword: \"test-password\",\n\t\t\t\t\tModel:    \"test-model\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}).Return(nil)\n\n\tmockAI.On(\"Configure\", mock.AnythingOfType(\"*ai.AIProvider\")).Return(nil)\n\tmockAI.On(\"GetCompletion\", mock.Anything, testQuery).Return(testResponse, nil)\n\n\t// Create handler and call Query\n\thandler := &Handler{}\n\tresponse, err := handler.Query(context.Background(), &schemav1.QueryRequest{\n\t\tBackend: testBackend,\n\t\tQuery:   testQuery,\n\t})\n\n\t// Assertions\n\tassert.NoError(t, err)\n\tassert.NotNil(t, response)\n\tassert.Equal(t, testResponse, response.Response)\n\tassert.Equal(t, \"\", response.Error.Message)\n\n\t// Verify mocks\n\tmockAI.AssertExpectations(t)\n\tmockFactory.AssertExpectations(t)\n\tmockConfig.AssertExpectations(t)\n}\n\nfunc TestQuery_UnmarshalError(t *testing.T) {\n\t// Setup mocks\n\tmockAI := new(MockAI)\n\tmockFactory := new(MockAIClientFactory)\n\tmockConfig := new(MockConfigProvider)\n\n\t// Set test implementations\n\tai.SetTestAIClientFactory(mockFactory)\n\tai.SetTestConfigProvider(mockConfig)\n\tdefer ai.ResetTestImplementations()\n\n\t// Setup expectations\n\tmockFactory.On(\"NewClient\", \"test-backend\").Return(mockAI)\n\tmockAI.On(\"Close\").Return()\n\n\t// Mock unmarshal error\n\tmockConfig.On(\"UnmarshalKey\", \"ai\", mock.Anything).Return(errors.New(\"unmarshal error\"))\n\n\t// Create handler and call Query\n\thandler := &Handler{}\n\tresponse, err := handler.Query(context.Background(), &schemav1.QueryRequest{\n\t\tBackend: \"test-backend\",\n\t\tQuery:   \"test query\",\n\t})\n\n\t// Assertions\n\tassert.NoError(t, err)\n\tassert.NotNil(t, response)\n\tassert.Equal(t, \"\", response.Response)\n\tassert.Contains(t, response.Error.Message, \"Failed to unmarshal AI configuration\")\n\n\t// Verify mocks\n\tmockAI.AssertExpectations(t)\n\tmockFactory.AssertExpectations(t)\n\tmockConfig.AssertExpectations(t)\n}\n\nfunc TestQuery_ProviderNotFound(t *testing.T) {\n\t// Setup mocks\n\tmockAI := new(MockAI)\n\tmockFactory := new(MockAIClientFactory)\n\tmockConfig := new(MockConfigProvider)\n\n\t// Set test implementations\n\tai.SetTestAIClientFactory(mockFactory)\n\tai.SetTestConfigProvider(mockConfig)\n\tdefer ai.ResetTestImplementations()\n\n\t// Define test data\n\ttestBackend := \"test-backend\"\n\n\t// Setup expectations\n\tmockFactory.On(\"NewClient\", testBackend).Return(mockAI)\n\tmockAI.On(\"Close\").Return()\n\n\t// Set up configuration with no matching provider\n\tmockConfig.On(\"UnmarshalKey\", \"ai\", mock.Anything).Run(func(args mock.Arguments) {\n\t\tconfig := args.Get(1).(*ai.AIConfiguration)\n\t\t*config = ai.AIConfiguration{\n\t\t\tProviders: []ai.AIProvider{\n\t\t\t\t{\n\t\t\t\t\tName: \"other-backend\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}).Return(nil)\n\n\t// Create handler and call Query\n\thandler := &Handler{}\n\tresponse, err := handler.Query(context.Background(), &schemav1.QueryRequest{\n\t\tBackend: testBackend,\n\t\tQuery:   \"test query\",\n\t})\n\n\t// Assertions\n\tassert.NoError(t, err)\n\tassert.NotNil(t, response)\n\tassert.Equal(t, \"\", response.Response)\n\tassert.Contains(t, response.Error.Message, \"AI provider test-backend not found in configuration\")\n\n\t// Verify mocks\n\tmockAI.AssertExpectations(t)\n\tmockFactory.AssertExpectations(t)\n\tmockConfig.AssertExpectations(t)\n}\n\nfunc TestQuery_ConfigureError(t *testing.T) {\n\t// Setup mocks\n\tmockAI := new(MockAI)\n\tmockFactory := new(MockAIClientFactory)\n\tmockConfig := new(MockConfigProvider)\n\n\t// Set test implementations\n\tai.SetTestAIClientFactory(mockFactory)\n\tai.SetTestConfigProvider(mockConfig)\n\tdefer ai.ResetTestImplementations()\n\n\t// Define test data\n\ttestBackend := \"test-backend\"\n\n\t// Setup expectations\n\tmockFactory.On(\"NewClient\", testBackend).Return(mockAI)\n\tmockAI.On(\"Close\").Return()\n\n\t// Set up configuration with a valid provider\n\tmockConfig.On(\"UnmarshalKey\", \"ai\", mock.Anything).Run(func(args mock.Arguments) {\n\t\tconfig := args.Get(1).(*ai.AIConfiguration)\n\t\t*config = ai.AIConfiguration{\n\t\t\tProviders: []ai.AIProvider{\n\t\t\t\t{\n\t\t\t\t\tName: testBackend,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}).Return(nil)\n\n\t// Mock configure error\n\tmockAI.On(\"Configure\", mock.AnythingOfType(\"*ai.AIProvider\")).Return(errors.New(\"configure error\"))\n\n\t// Create handler and call Query\n\thandler := &Handler{}\n\tresponse, err := handler.Query(context.Background(), &schemav1.QueryRequest{\n\t\tBackend: testBackend,\n\t\tQuery:   \"test query\",\n\t})\n\n\t// Assertions\n\tassert.NoError(t, err)\n\tassert.NotNil(t, response)\n\tassert.Equal(t, \"\", response.Response)\n\tassert.Contains(t, response.Error.Message, \"Failed to configure AI client\")\n\n\t// Verify mocks\n\tmockAI.AssertExpectations(t)\n\tmockFactory.AssertExpectations(t)\n\tmockConfig.AssertExpectations(t)\n}\n\nfunc TestQuery_GetCompletionError(t *testing.T) {\n\t// Setup mocks\n\tmockAI := new(MockAI)\n\tmockFactory := new(MockAIClientFactory)\n\tmockConfig := new(MockConfigProvider)\n\n\t// Set test implementations\n\tai.SetTestAIClientFactory(mockFactory)\n\tai.SetTestConfigProvider(mockConfig)\n\tdefer ai.ResetTestImplementations()\n\n\t// Define test data\n\ttestBackend := \"test-backend\"\n\ttestQuery := \"test query\"\n\n\t// Setup expectations\n\tmockFactory.On(\"NewClient\", testBackend).Return(mockAI)\n\tmockAI.On(\"Close\").Return()\n\n\t// Set up configuration with a valid provider\n\tmockConfig.On(\"UnmarshalKey\", \"ai\", mock.Anything).Run(func(args mock.Arguments) {\n\t\tconfig := args.Get(1).(*ai.AIConfiguration)\n\t\t*config = ai.AIConfiguration{\n\t\t\tProviders: []ai.AIProvider{\n\t\t\t\t{\n\t\t\t\t\tName: testBackend,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}).Return(nil)\n\n\tmockAI.On(\"Configure\", mock.AnythingOfType(\"*ai.AIProvider\")).Return(nil)\n\tmockAI.On(\"GetCompletion\", mock.Anything, testQuery).Return(\"\", errors.New(\"completion error\"))\n\n\t// Create handler and call Query\n\thandler := &Handler{}\n\tresponse, err := handler.Query(context.Background(), &schemav1.QueryRequest{\n\t\tBackend: testBackend,\n\t\tQuery:   testQuery,\n\t})\n\n\t// Assertions\n\tassert.NoError(t, err)\n\tassert.NotNil(t, response)\n\tassert.Equal(t, \"\", response.Response)\n\tassert.Equal(t, \"completion error\", response.Error.Message)\n\n\t// Verify mocks\n\tmockAI.AssertExpectations(t)\n\tmockFactory.AssertExpectations(t)\n\tmockConfig.AssertExpectations(t)\n}"
  },
  {
    "path": "pkg/server/server.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/grpc-ecosystem/grpc-gateway/v2/runtime\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/server/analyze\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/server/config\"\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/server/query\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\t//nolint:staticcheck // Ignoring SA1019 for compatibility reasons\n\tgw2 \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2/schema/v1/server_analyzer_service/schemav1gateway\"\n\t//nolint:staticcheck // Ignoring SA1019 for compatibility reasons\n\tgw \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2/schema/v1/server_config_service/schemav1gateway\"\n\trpc \"buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc\"\n\t\"github.com/go-logr/zapr\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"go.uber.org/zap\"\n\t\"golang.org/x/net/http2\"\n\t\"golang.org/x/net/http2/h2c\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/reflection\"\n)\n\ntype Config struct {\n\tPort           string\n\tMetricsPort    string\n\tBackend        string\n\tKey            string\n\tToken          string\n\tOutput         string\n\tConfigHandler  *config.Handler\n\tAnalyzeHandler *analyze.Handler\n\tQueryHandler   *query.Handler\n\tLogger         *zap.Logger\n\t// Filters can be injected into the server to limit analysis to specific analyzers\n\tFilters        []string\n\tmetricsServer  *http.Server\n\tlistener       net.Listener\n\tEnableHttp     bool\n}\n\ntype Health struct {\n\tStatus  string `json:\"status\"`\n\tSuccess int    `json:\"success\"`\n\tFailure int    `json:\"failure\"`\n}\n\n//nolint:unused\nvar health = Health{\n\tStatus:  \"ok\",\n\tSuccess: 0,\n\tFailure: 0,\n}\n\nfunc (s *Config) Shutdown() error {\n\treturn s.listener.Close()\n}\n\n// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC\n// connections or otherHandler otherwise.\nfunc grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.ProtoMajor == 2 && strings.Contains(r.Header.Get(\"Content-Type\"), \"application/grpc\") {\n\t\t\tgrpcServer.ServeHTTP(w, r)\n\t\t} else {\n\t\t\totherHandler.ServeHTTP(w, r)\n\t\t}\n\t})\n}\n\nfunc (s *Config) Serve() error {\n\tctrl.SetLogger(zapr.NewLogger(s.Logger))\n\n\tvar lis net.Listener\n\tvar err error\n\taddress := fmt.Sprintf(\":%s\", s.Port)\n\tlis, err = net.Listen(\"tcp\", address)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.ConfigHandler = &config.Handler{}\n\ts.AnalyzeHandler = &analyze.Handler{}\n\ts.QueryHandler = &query.Handler{}\n\ts.listener = lis\n\ts.Logger.Info(fmt.Sprintf(\"binding api to %s\", s.Port))\n\tgrpcServerUnaryInterceptor := grpc.UnaryInterceptor(LogInterceptor(s.Logger))\n\tgrpcServer := grpc.NewServer(grpcServerUnaryInterceptor)\n\treflection.Register(grpcServer)\n\trpc.RegisterServerConfigServiceServer(grpcServer, s.ConfigHandler)\n\trpc.RegisterServerAnalyzerServiceServer(grpcServer, s.AnalyzeHandler)\n\trpc.RegisterServerQueryServiceServer(grpcServer, s.QueryHandler)\n\tif s.EnableHttp {\n\t\ts.Logger.Info(\"enabling rest/http api\")\n\t\tgwmux := runtime.NewServeMux()\n\t\terr = gw.RegisterServerConfigServiceHandlerFromEndpoint(context.Background(), gwmux, fmt.Sprintf(\"localhost:%s\", s.Port),\n\t\t\t[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())})\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to register gateway:\", err)\n\t\t}\n\t\terr = gw2.RegisterServerAnalyzerServiceHandlerFromEndpoint(context.Background(), gwmux, fmt.Sprintf(\"localhost:%s\", s.Port),\n\t\t\t[]grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())})\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to register gateway:\", err)\n\t\t}\n\n\t\tsrv := &http.Server{\n\t\t\tAddr:    address,\n\t\t\tHandler: h2c.NewHandler(grpcHandlerFunc(grpcServer, gwmux), &http2.Server{}),\n\t\t}\n\n\t\tif err := srv.Serve(lis); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif err := grpcServer.Serve(\n\t\t\tlis,\n\t\t); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Config) ServeMetrics() error {\n\ts.Logger.Info(fmt.Sprintf(\"binding metrics to %s\", s.MetricsPort))\n\ts.metricsServer = &http.Server{\n\t\tReadHeaderTimeout: 3 * time.Second,\n\t\tAddr:              fmt.Sprintf(\":%s\", s.MetricsPort),\n\t}\n\ts.metricsServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/healthz\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\tcase \"/metrics\":\n\t\t\tpromhttp.Handler().ServeHTTP(w, r)\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\t})\n\tif err := s.metricsServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/server/server_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/ai\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/reflection/grpc_reflection_v1alpha\"\n)\n\nfunc TestServe(t *testing.T) {\n\tlogger, _ := zap.NewDevelopment()\n\tdefer func() {\n\t\terr := logger.Sync()\n\t\tif err != nil {\n\t\t\tt.Logf(\"logger.Sync() error: %v\", err)\n\t\t}\n\t}()\n\n\ts := &Config{\n\t\tPort:       \"50059\",\n\t\tLogger:     logger,\n\t\tEnableHttp: false,\n\t}\n\n\tgo func() {\n\t\terr := s.Serve()\n\t\ttime.Sleep(time.Second * 2)\n\t\tassert.NoError(t, err, \"Serve should not return an error\")\n\t}()\n\n\t// Wait until the server is ready to accept connections\n\terr := waitForPort(\"localhost:50059\", 10*time.Second)\n\tassert.NoError(t, err, \"Server should start without error\")\n\n\tconn, err := grpc.Dial(\"localhost:50059\", grpc.WithInsecure())\n\tassert.NoError(t, err, \"Should be able to dial the server\")\n\tdefer func() {\n\t\tif err := conn.Close(); err != nil {\n\t\t\tt.Logf(\"failed to close connection: %v\", err)\n\t\t}\n\t}()\n\n\t// Test a simple gRPC reflection request\n\tcli := grpc_reflection_v1alpha.NewServerReflectionClient(conn)\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tresp, err := cli.ServerReflectionInfo(ctx)\n\tassert.NoError(t, err, \"Should be able to get server reflection info\")\n\tassert.NotNil(t, resp, \"Response should not be nil\")\n\n\t// Cleanup\n\terr = s.Shutdown()\n\tassert.NoError(t, err, \"Shutdown should not return an error\")\n}\n\n// TestMCPServerCreation tests the creation of an MCP server\nfunc TestMCPServerCreation(t *testing.T) {\n\tlogger, _ := zap.NewDevelopment()\n\tdefer func() {\n\t\terr := logger.Sync()\n\t\tif err != nil {\n\t\t\tt.Logf(\"logger.Sync() error: %v\", err)\n\t\t}\n\t}()\n\n\taiProvider := &ai.AIProvider{\n\t\tName:     \"test-provider\",\n\t\tPassword: \"test-password\",\n\t\tModel:    \"test-model\",\n\t}\n\n\t// Test HTTP mode\n\tmcpServer, err := NewMCPServer(\"8088\", aiProvider, true, logger)\n\tassert.NoError(t, err, \"Should be able to create MCP server with HTTP transport\")\n\tassert.NotNil(t, mcpServer, \"MCP server should not be nil\")\n\tassert.True(t, mcpServer.useHTTP, \"MCP server should be in HTTP mode\")\n\tassert.Equal(t, \"8088\", mcpServer.port, \"Port should be set correctly\")\n\n\t// Test stdio mode\n\tmcpServerStdio, err := NewMCPServer(\"8088\", aiProvider, false, logger)\n\tassert.NoError(t, err, \"Should be able to create MCP server with stdio transport\")\n\tassert.NotNil(t, mcpServerStdio, \"MCP server should not be nil\")\n\tassert.False(t, mcpServerStdio.useHTTP, \"MCP server should be in stdio mode\")\n}\n\n// TestMCPServerBasicHTTP tests basic HTTP connectivity to the MCP server\nfunc TestMCPServerBasicHTTP(t *testing.T) {\n\tlogger, _ := zap.NewDevelopment()\n\tdefer func() {\n\t\terr := logger.Sync()\n\t\tif err != nil {\n\t\t\tt.Logf(\"logger.Sync() error: %v\", err)\n\t\t}\n\t}()\n\n\taiProvider := &ai.AIProvider{\n\t\tName:     \"test-provider\",\n\t\tPassword: \"test-password\",\n\t\tModel:    \"test-model\",\n\t}\n\n\tmcpServer, err := NewMCPServer(\"8091\", aiProvider, true, logger)\n\tassert.NoError(t, err, \"Should be able to create MCP server\")\n\n\t// For HTTP mode, the server is already started in NewMCPServer\n\t// No need to call Start() as it's already running in a goroutine\n\n\t// Wait for the server to start\n\terr = waitForPort(\"localhost:8091\", 10*time.Second)\n\tif err != nil {\n\t\tt.Skipf(\"MCP server did not start within timeout: %v\", err)\n\t}\n\n\t// First, initialize the session\n\tinitRequest := `{\n\t\t\"jsonrpc\": \"2.0\",\n\t\t\"id\": 1,\n\t\t\"method\": \"initialize\",\n\t\t\"params\": {\n\t\t\t\"protocolVersion\": \"2025-03-26\",\n\t\t\t\"capabilities\": {\n\t\t\t\t\"tools\": {},\n\t\t\t\t\"resources\": {},\n\t\t\t\t\"prompts\": {}\n\t\t\t},\n\t\t\t\"clientInfo\": {\n\t\t\t\t\"name\": \"test-client\",\n\t\t\t\t\"version\": \"1.0.0\"\n\t\t\t}\n\t\t}\n\t}`\n\n\tinitResp, err := http.Post(\"http://localhost:8091/mcp\", \"application/json\", bytes.NewBufferString(initRequest))\n\tif err != nil {\n\t\tt.Logf(\"Initialize request failed: %v\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := initResp.Body.Close(); err != nil {\n\t\t\tt.Logf(\"Error closing init response body: %v\", err)\n\t\t}\n\t}()\n\n\t// Read initialization response\n\tinitBody, err := io.ReadAll(initResp.Body)\n\tif err != nil {\n\t\tt.Logf(\"Failed to read init response body: %v\", err)\n\t} else {\n\t\tt.Logf(\"Init response status: %d, body: %s\", initResp.StatusCode, string(initBody))\n\t}\n\n\t// Extract session ID from response headers if present\n\tsessionID := initResp.Header.Get(\"Mcp-Session-Id\")\n\tif sessionID == \"\" {\n\t\tt.Logf(\"No session ID in response headers\")\n\t}\n\n\t// Now test tools/list with session ID if available\n\theaders := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Accept\":       \"application/json,text/event-stream\",\n\t}\n\tif sessionID != \"\" {\n\t\theaders[\"Mcp-Session-Id\"] = sessionID\n\t}\n\n\treq, err := http.NewRequest(\"POST\", \"http://localhost:8091/mcp\", bytes.NewBufferString(`{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}`))\n\tif err != nil {\n\t\tt.Logf(\"Failed to create request: %v\", err)\n\t\treturn\n\t}\n\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Logf(\"MCP endpoint test skipped (server might not be fully ready): %v\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr := resp.Body.Close()\n\t\tif err != nil {\n\t\t\tt.Logf(\"resp.Body.Close() error: %v\", err)\n\t\t}\n\t}()\n\n\t// Read response body for debugging\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Logf(\"Failed to read response body: %v\", err)\n\t} else {\n\t\tt.Logf(\"Response status: %d, body: %s\", resp.StatusCode, string(body))\n\t}\n\n\t// Accept both 200 and 404 as valid responses (404 means endpoint not implemented)\n\tif resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {\n\t\tt.Errorf(\"MCP endpoint returned unexpected status: %d\", resp.StatusCode)\n\t}\n\n\t// Cleanup\n\terr = mcpServer.Close()\n\tassert.NoError(t, err, \"MCP server should close without error\")\n}\n\n// TestMCPServerToolCall tests calling a specific tool (analyze) through the MCP server\nfunc TestMCPServerToolCall(t *testing.T) {\n\tlogger, _ := zap.NewDevelopment()\n\tdefer func() {\n\t\terr := logger.Sync()\n\t\tif err != nil {\n\t\t\tt.Logf(\"logger.Sync() error: %v\", err)\n\t\t}\n\t}()\n\n\taiProvider := &ai.AIProvider{\n\t\tName:     \"test-provider\",\n\t\tPassword: \"test-password\",\n\t\tModel:    \"test-model\",\n\t}\n\n\tmcpServer, err := NewMCPServer(\"8090\", aiProvider, true, logger)\n\tassert.NoError(t, err, \"Should be able to create MCP server\")\n\n\t// For HTTP mode, the server is already started in NewMCPServer\n\t// No need to call Start() as it's already running in a goroutine\n\n\t// Wait for the server to start\n\terr = waitForPort(\"localhost:8090\", 10*time.Second)\n\tif err != nil {\n\t\tt.Skipf(\"MCP server did not start within timeout: %v\", err)\n\t}\n\n\t// First, initialize the session\n\tinitRequest := `{\n\t\t\"jsonrpc\": \"2.0\",\n\t\t\"id\": 1,\n\t\t\"method\": \"initialize\",\n\t\t\"params\": {\n\t\t\t\"protocolVersion\": \"2025-03-26\",\n\t\t\t\"capabilities\": {\n\t\t\t\t\"tools\": {},\n\t\t\t\t\"resources\": {},\n\t\t\t\t\"prompts\": {}\n\t\t\t},\n\t\t\t\"clientInfo\": {\n\t\t\t\t\"name\": \"test-client\",\n\t\t\t\t\"version\": \"1.0.0\"\n\t\t\t}\n\t\t}\n\t}`\n\n\tinitResp, err := http.Post(\"http://localhost:8090/mcp\", \"application/json\", bytes.NewBufferString(initRequest))\n\tif err != nil {\n\t\tt.Logf(\"Initialize request failed: %v\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := initResp.Body.Close(); err != nil {\n\t\t\tt.Logf(\"Error closing init response body: %v\", err)\n\t\t}\n\t}()\n\n\t// Extract session ID from response headers if present\n\tsessionID := initResp.Header.Get(\"Mcp-Session-Id\")\n\n\t// Test calling the analyze tool with proper JSON-RPC format\n\tanalyzeRequest := `{\n\t\t\"jsonrpc\": \"2.0\",\n\t\t\"id\": 2,\n\t\t\"method\": \"tools/call\",\n\t\t\"params\": {\n\t\t\t\"name\": \"analyze\",\n\t\t\t\"arguments\": {\n\t\t\t\t\"namespace\": \"default\",\n\t\t\t\t\"backend\": \"openai\",\n\t\t\t\t\"language\": \"english\",\n\t\t\t\t\"explain\": true,\n\t\t\t\t\"maxConcurrency\": 10\n\t\t\t}\n\t\t}\n\t}`\n\n\t// Create request with session ID if available\n\treq, err := http.NewRequest(\"POST\", \"http://localhost:8090/mcp\", bytes.NewBufferString(analyzeRequest))\n\tif err != nil {\n\t\tt.Logf(\"Failed to create request: %v\", err)\n\t\treturn\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json,text/event-stream\")\n\tif sessionID != \"\" {\n\t\treq.Header.Set(\"Mcp-Session-Id\", sessionID)\n\t}\n\n\tclient := &http.Client{}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tt.Logf(\"Analyze tool call test skipped (server might not be fully ready): %v\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr := resp.Body.Close()\n\t\tif err != nil {\n\t\t\tt.Logf(\"resp.Body.Close() error: %v\", err)\n\t\t}\n\t}()\n\n\t// Accept both 200 and 404 as valid responses\n\tif resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {\n\t\tt.Errorf(\"Analyze tool call returned unexpected status: %d\", resp.StatusCode)\n\t}\n\n\t// Cleanup\n\terr = mcpServer.Close()\n\tassert.NoError(t, err, \"MCP server should close without error\")\n}\n\nfunc waitForPort(address string, timeout time.Duration) error {\n\tstart := time.Now()\n\tfor {\n\t\tconn, err := net.Dial(\"tcp\", address)\n\t\tif err == nil {\n\t\t\t_ = conn.Close()\n\t\t\treturn nil\n\t\t}\n\t\tif time.Since(start) > timeout {\n\t\t\treturn err\n\t\t}\n\t\ttime.Sleep(100 * time.Millisecond)\n\t}\n}\n"
  },
  {
    "path": "pkg/util/util.go",
    "content": "/*\nCopyright 2023 The K8sGPT 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 util\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/labels\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tk \"k8s.io/client-go/kubernetes\"\n)\n\nvar anonymizePattern = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;':\\\",./<>?\")\n\nfunc GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) {\n\tif meta.OwnerReferences != nil {\n\t\tfor _, owner := range meta.OwnerReferences {\n\t\t\tswitch owner.Kind {\n\t\t\tcase \"ReplicaSet\":\n\t\t\t\trs, err := client.GetClient().AppsV1().ReplicaSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif rs.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, rs.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"ReplicaSet/\" + rs.Name, true\n\n\t\t\tcase \"Deployment\":\n\t\t\t\tdep, err := client.GetClient().AppsV1().Deployments(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif dep.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, dep.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"Deployment/\" + dep.Name, true\n\n\t\t\tcase \"StatefulSet\":\n\t\t\t\tsts, err := client.GetClient().AppsV1().StatefulSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif sts.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, sts.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"StatefulSet/\" + sts.Name, true\n\n\t\t\tcase \"DaemonSet\":\n\t\t\t\tds, err := client.GetClient().AppsV1().DaemonSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif ds.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, ds.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"DaemonSet/\" + ds.Name, true\n\n\t\t\tcase \"Ingress\":\n\t\t\t\tds, err := client.GetClient().NetworkingV1().Ingresses(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif ds.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, ds.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"Ingress/\" + ds.Name, true\n\n\t\t\tcase \"MutatingWebhookConfiguration\":\n\t\t\t\tmw, err := client.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif mw.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, mw.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"MutatingWebhook/\" + mw.Name, true\n\n\t\t\tcase \"ValidatingWebhookConfiguration\":\n\t\t\t\tvw, err := client.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), owner.Name, metav1.GetOptions{})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", false\n\t\t\t\t}\n\t\t\t\tif vw.OwnerReferences != nil {\n\t\t\t\t\treturn GetParent(client, vw.ObjectMeta)\n\t\t\t\t}\n\t\t\t\treturn \"ValidatingWebhook/\" + vw.Name, true\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc RemoveDuplicates(slice []string) ([]string, []string) {\n\tset := make(map[string]bool)\n\tduplicates := []string{}\n\tfor _, val := range slice {\n\t\tif _, ok := set[val]; !ok {\n\t\t\tset[val] = true\n\t\t} else {\n\t\t\tduplicates = append(duplicates, val)\n\t\t}\n\t}\n\tuniqueSlice := make([]string, 0, len(set))\n\tfor val := range set {\n\t\tuniqueSlice = append(uniqueSlice, val)\n\t}\n\treturn uniqueSlice, duplicates\n}\n\nfunc SliceDiff(source, dest []string) []string {\n\tmb := make(map[string]struct{}, len(dest))\n\tfor _, x := range dest {\n\t\tmb[x] = struct{}{}\n\t}\n\tvar diff []string\n\tfor _, x := range source {\n\t\tif _, found := mb[x]; !found {\n\t\t\tdiff = append(diff, x)\n\t\t}\n\t}\n\treturn diff\n}\n\nfunc MaskString(input string) string {\n\tkey := make([]byte, len(input))\n\tresult := make([]rune, len(input))\n\t_, err := rand.Read(key)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfor i := range result {\n\t\tresult[i] = anonymizePattern[int(key[i])%len(anonymizePattern)]\n\t}\n\treturn base64.StdEncoding.EncodeToString([]byte(string(result)))\n}\n\nfunc ReplaceIfMatch(text string, pattern string, replacement string) string {\n\tre := regexp.MustCompile(fmt.Sprintf(`%s(\\b)`, pattern))\n\tif re.MatchString(text) {\n\t\ttext = re.ReplaceAllString(text, replacement)\n\t}\n\treturn text\n}\n\nfunc GetCacheKey(provider string, language string, sEnc string) string {\n\tdata := fmt.Sprintf(\"%s-%s-%s\", provider, language, sEnc)\n\n\thash := sha256.Sum256([]byte(data))\n\n\treturn hex.EncodeToString(hash[:])\n}\n\nfunc GetPodListByLabels(client k.Interface,\n\tnamespace string,\n\tlabels map[string]string,\n) (*v1.PodList, error) {\n\tpods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{\n\t\tLabelSelector: metav1.FormatLabelSelector(&metav1.LabelSelector{\n\t\t\tMatchLabels: labels,\n\t\t}),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn pods, nil\n}\n\nfunc FileExists(path string) (bool, error) {\n\tif _, err := os.Stat(path); err == nil {\n\t\treturn true, nil\n\t} else if errors.Is(err, os.ErrNotExist) {\n\t\treturn false, nil\n\t} else {\n\t\treturn false, err\n\t}\n}\n\nfunc EnsureDirExists(dir string) error {\n\terr := os.MkdirAll(dir, 0o755)\n\n\tif errors.Is(err, os.ErrExist) {\n\t\treturn nil\n\t}\n\n\treturn err\n}\n\nfunc MapToString(m map[string]string) string {\n\t// Handle empty map case\n\tif len(m) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar pairs []string\n\tfor k, v := range m {\n\t\tpairs = append(pairs, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\n\t// Efficient string joining\n\treturn strings.Join(pairs, \",\")\n}\n\nfunc LabelsIncludeAny(predefinedSelector, Labels map[string]string) bool {\n\t// Check if any label in the predefinedSelector exists in Labels\n\tfor key := range predefinedSelector {\n\t\tif _, exists := Labels[key]; exists {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {\n\n\t// get the list of events\n\tevents, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,\n\t\tmetav1.ListOptions{\n\t\t\tFieldSelector: \"involvedObject.name=\" + name,\n\t\t})\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// find most recent event\n\tvar latestEvent *v1.Event\n\tfor _, event := range events.Items {\n\t\tif latestEvent == nil {\n\t\t\t// this is required, as a pointer to a loop variable would always yield the latest value in the range\n\t\t\te := event\n\t\t\tlatestEvent = &e\n\t\t}\n\t\tif event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {\n\t\t\t// this is required, as a pointer to a loop variable would always yield the latest value in the range\n\t\t\te := event\n\t\t\tlatestEvent = &e\n\t\t}\n\t}\n\treturn latestEvent, nil\n}\n\n// NewHeaders parses a slice of strings in the format \"key:value\" into []http.Header\n// It handles headers with the same key by appending values\nfunc NewHeaders(customHeaders []string) []http.Header {\n\theaders := make(map[string][]string)\n\n\tfor _, header := range customHeaders {\n\t\tvals := strings.SplitN(header, \":\", 2)\n\t\tif len(vals) != 2 {\n\t\t\t//TODO: Handle error instead of ignoring it\n\t\t\tcontinue\n\t\t}\n\t\tkey := strings.TrimSpace(vals[0])\n\t\tvalue := strings.TrimSpace(vals[1])\n\n\t\tif _, ok := headers[key]; !ok {\n\t\t\theaders[key] = []string{}\n\t\t}\n\t\theaders[key] = append(headers[key], value)\n\t}\n\n\t// Convert map to []http.Header format\n\tvar result []http.Header\n\tfor key, values := range headers {\n\t\theader := make(http.Header)\n\t\tfor _, value := range values {\n\t\t\theader.Add(key, value)\n\t\t}\n\t\tresult = append(result, header)\n\t}\n\n\treturn result\n}\n\nfunc LabelStrToSelector(labelStr string) labels.Selector {\n\tif labelStr == \"\" {\n\t\treturn nil\n\t}\n\tlabelSelectorMap := make(map[string]string)\n\tfor _, s := range strings.Split(labelStr, \",\") {\n\t\tparts := strings.SplitN(s, \"=\", 2)\n\t\tif len(parts) == 2 {\n\t\t\tlabelSelectorMap[parts[0]] = parts[1]\n\t\t}\n\t}\n\treturn labels.SelectorFromSet(labels.Set(labelSelectorMap))\n}\n\n// CaptureOutput captures the output of a function that writes to stdout\nfunc CaptureOutput(f func()) string {\n\told := os.Stdout\n\tr, w, err := os.Pipe()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to create pipe: %v\", err))\n\t}\n\tos.Stdout = w\n\t// Ensure os.Stdout is restored even if panic occurs\n\tdefer func() {\n\t\tos.Stdout = old\n\t}()\n\n\tf()\n\n\tif err := w.Close(); err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to close writer: %v\", err))\n\t}\n\tvar buf bytes.Buffer\n\tif _, err := buf.ReadFrom(r); err != nil {\n\t\tpanic(fmt.Sprintf(\"failed to read from pipe: %v\", err))\n\t}\n\treturn buf.String()\n}\n\n// Contains checks if substr is present in s\nfunc Contains(s, substr string) bool {\n\treturn bytes.Contains([]byte(s), []byte(substr))\n}\n"
  },
  {
    "path": "pkg/util/util_test.go",
    "content": "/*\nCopyright 2024 The K8sGPT 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 util\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes\"\n\t\"github.com/stretchr/testify/require\"\n\tadmissionregistrationv1 \"k8s.io/api/admissionregistration/v1\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n\tv1 \"k8s.io/api/core/v1\"\n\tnetworkingv1 \"k8s.io/api/networking/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\nfunc TestGetParent(t *testing.T) {\n\townerName := \"test-name\"\n\tnamespace := \"test\"\n\tclientset := fake.NewSimpleClientset(\n\t\t&appsv1.ReplicaSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      ownerName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t},\n\t\t&appsv1.Deployment{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      ownerName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t},\n\t\t&appsv1.StatefulSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      ownerName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t},\n\t\t&appsv1.DaemonSet{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      ownerName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t},\n\t\t&networkingv1.Ingress{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      ownerName,\n\t\t\t\tNamespace: namespace,\n\t\t\t},\n\t\t},\n\t\t&admissionregistrationv1.MutatingWebhookConfiguration{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: ownerName,\n\t\t\t},\n\t\t},\n\t\t&admissionregistrationv1.ValidatingWebhookConfiguration{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName: ownerName,\n\t\t\t},\n\t\t},\n\t)\n\tkubeClient := kubernetes.Client{\n\t\tClient: clientset,\n\t}\n\n\ttests := []struct {\n\t\tname           string\n\t\tkind           string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\tkind:           \"Unknown\",\n\t\t\texpectedOutput: \"\",\n\t\t},\n\t\t{\n\t\t\tkind: \"ReplicaSet\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"ReplicaSet\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"ReplicaSet/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"Deployment\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"Deployment\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"Deployment/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"StatefulSet\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"StatefulSet\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"StatefulSet/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"DaemonSet\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"DaemonSet\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"DaemonSet/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"Ingress\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"Ingress\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"Ingress/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"MutatingWebhookConfiguration\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"MutatingWebhookConfiguration\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"MutatingWebhook/test-name\",\n\t\t},\n\t\t{\n\t\t\tkind: \"ValidatingWebhookConfiguration\",\n\t\t},\n\t\t{\n\t\t\tkind:           \"ValidatingWebhookConfiguration\",\n\t\t\tname:           ownerName,\n\t\t\texpectedOutput: \"ValidatingWebhook/test-name\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.kind, func(t *testing.T) {\n\t\t\tmeta := metav1.ObjectMeta{\n\t\t\t\tNamespace: namespace,\n\t\t\t\tName:      ownerName,\n\t\t\t\tOwnerReferences: []metav1.OwnerReference{\n\t\t\t\t\t{\n\t\t\t\t\t\tKind: tt.kind,\n\t\t\t\t\t\tName: tt.name,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\toutput, ok := GetParent(&kubeClient, meta)\n\t\t\tif meta.OwnerReferences[0].Name != \"\" {\n\t\t\t\trequire.Equal(t, true, ok)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, false, ok)\n\t\t\t}\n\t\t\trequire.Equal(t, tt.expectedOutput, output)\n\t\t})\n\t}\n}\n\nfunc TestRemoveDuplicates(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tslice              []string\n\t\texpectedDuplicates []string\n\t}{\n\t\t{\n\t\t\tname:               \"all empty\",\n\t\t\texpectedDuplicates: []string{},\n\t\t},\n\t\t{\n\t\t\tname:               \"all unique\",\n\t\t\tslice:              []string{\"temp\", \"value\"},\n\t\t\texpectedDuplicates: []string{},\n\t\t},\n\t\t{\n\t\t\tname:               \"slice not unique\",\n\t\t\tslice:              []string{\"temp\", \"mango\", \"mango\"},\n\t\t\texpectedDuplicates: []string{\"mango\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, duplicates := RemoveDuplicates(tt.slice)\n\t\t\trequire.Equal(t, tt.expectedDuplicates, duplicates)\n\t\t})\n\t}\n}\n\nfunc TestSliceDiff(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tsource       []string\n\t\tdest         []string\n\t\texpectedDiff []string\n\t}{\n\t\t{\n\t\t\tname: \"all empty\",\n\t\t},\n\t\t{\n\t\t\tname:         \"non empty\",\n\t\t\tsource:       []string{\"temp\"},\n\t\t\tdest:         []string{\"random\"},\n\t\t\texpectedDiff: []string{\"temp\"},\n\t\t},\n\t\t{\n\t\t\tname:   \"no diff\",\n\t\t\tsource: []string{\"temp\", \"random\"},\n\t\t\tdest:   []string{\"random\", \"temp\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.expectedDiff, SliceDiff(tt.source, tt.dest))\n\t\t})\n\t}\n}\n\nfunc TestReplaceIfMatch(t *testing.T) {\n\ttests := []struct {\n\t\ttext           string\n\t\tpattern        string\n\t\treplacement    string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\ttext:           \"\",\n\t\t\texpectedOutput: \"\",\n\t\t},\n\t\t{\n\t\t\ttext:           \"some value\",\n\t\t\tpattern:        \"new\",\n\t\t\treplacement:    \"latest\",\n\t\t\texpectedOutput: \"some value\",\n\t\t},\n\t\t{\n\t\t\ttext:           \"new value\",\n\t\t\tpattern:        \"value\",\n\t\t\treplacement:    \"day\",\n\t\t\texpectedOutput: \"new day\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.text, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.expectedOutput, ReplaceIfMatch(tt.text, tt.pattern, tt.replacement))\n\t\t})\n\t}\n}\n\nfunc TestGetCacheKey(t *testing.T) {\n\ttests := []struct {\n\t\tprovider       string\n\t\tlanguage       string\n\t\tsEnc           string\n\t\texpectedOutput string\n\t}{\n\t\t{\n\t\t\texpectedOutput: \"d8156bae0c4243d3742fc4e9774d8aceabe0410249d720c855f98afc88ff846c\",\n\t\t},\n\t\t{\n\t\t\tprovider:       \"provider\",\n\t\t\tlanguage:       \"english\",\n\t\t\tsEnc:           \"encoding\",\n\t\t\texpectedOutput: \"39415cc324b1553b93e80e46049e4e4dbb752dc7d0424b2c6ac96d745c6392aa\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.language, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.expectedOutput, GetCacheKey(tt.provider, tt.language, tt.sEnc))\n\t\t})\n\t}\n}\n\nfunc TestGetPodListByLabels(t *testing.T) {\n\tnamespace1 := \"test1\"\n\tnamespace2 := \"test2\"\n\tclientset := fake.NewSimpleClientset(\n\t\t&v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"Pod1\",\n\t\t\t\tNamespace: namespace1,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"Name\":      \"Pod1\",\n\t\t\t\t\t\"Namespace\": namespace1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"Pod2\",\n\t\t\t\tNamespace: namespace2,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"Name\":      \"Pod2\",\n\t\t\t\t\t\"Namespace\": namespace2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"Pod3\",\n\t\t\t\tNamespace: namespace1,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"Name\":      \"Pod3\",\n\t\t\t\t\t\"Namespace\": namespace1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t&v1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tName:      \"Pod4\",\n\t\t\t\tNamespace: namespace2,\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"Name\":      \"Pod4\",\n\t\t\t\t\t\"Namespace\": namespace2,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t)\n\n\ttests := []struct {\n\t\tname        string\n\t\tnamespace   string\n\t\tlabels      map[string]string\n\t\texpectedLen int\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname:      \"Name is Pod1\",\n\t\t\tnamespace: namespace1,\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"Name\": \"Pod1\",\n\t\t\t},\n\t\t\texpectedLen: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"Name is Pod2 in namespace1\",\n\t\t\tnamespace: namespace1,\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"Name\": \"Pod2\",\n\t\t\t},\n\t\t\texpectedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname:      \"Name is Pod2 in namespace 2\",\n\t\t\tnamespace: namespace2,\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"Name\": \"Pod2\",\n\t\t\t},\n\t\t\texpectedLen: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"All pods with namespace2 label in namespace1\",\n\t\t\tnamespace: namespace1,\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"Namespace\": namespace2,\n\t\t\t},\n\t\t\texpectedLen: 0,\n\t\t},\n\t\t{\n\t\t\tname:      \"All pods with namespace2 label in namespace2\",\n\t\t\tnamespace: namespace2,\n\t\t\tlabels: map[string]string{\n\t\t\t\t\"Namespace\": namespace2,\n\t\t\t},\n\t\t\texpectedLen: 2,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpl, err := GetPodListByLabels(clientset, tt.namespace, tt.labels)\n\t\t\tif tt.expectedErr == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, tt.expectedLen, len(pl.Items))\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.expectedErr)\n\t\t\t\trequire.Nil(t, pl)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFileExists(t *testing.T) {\n\ttests := []struct {\n\t\tfilePath  string\n\t\tisPresent bool\n\t\terr       string\n\t}{\n\t\t{\n\t\t\tfilePath:  \"\",\n\t\t\tisPresent: false,\n\t\t},\n\t\t{\n\t\t\tfilePath:  \"./util.go\",\n\t\t\tisPresent: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.filePath, func(t *testing.T) {\n\t\t\tisPresent, err := FileExists(tt.filePath)\n\t\t\tif tt.err == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.err)\n\t\t\t}\n\t\t\trequire.Equal(t, tt.isPresent, isPresent)\n\t\t})\n\t}\n}\n\nfunc TestEnsureDirExists(t *testing.T) {\n\ttests := []struct {\n\t\tdir string\n\t\terr string\n\t}{\n\t\t{\n\t\t\tdir: \"\",\n\t\t\terr: \"mkdir : no such file or directory\",\n\t\t},\n\t\t{\n\t\t\tdir: \"./\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.dir, func(t *testing.T) {\n\t\t\terr := EnsureDirExists(tt.dir)\n\t\t\tif tt.err == \"\" {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tt.err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMapToString(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tm      map[string]string\n\t\toutput string\n\t}{\n\t\t{\n\t\t\tname: \"empty map\",\n\t\t\tm:    map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname: \"non-empty map\",\n\t\t\tm: map[string]string{\n\t\t\t\t\"key\": \"value\",\n\t\t\t},\n\t\t\toutput: \"key=value\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.output, MapToString(tt.m))\n\t\t})\n\t}\n}\n\nfunc TestLabelsIncludeAny(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tm    map[string]string\n\t\tp    map[string]string\n\t\tok   bool\n\t}{\n\t\t{\n\t\t\tname: \"empty map\",\n\t\t\tm:    map[string]string{},\n\t\t\tp:    map[string]string{},\n\t\t\tok:   false,\n\t\t},\n\t\t{\n\t\t\tname: \"non-empty map\",\n\t\t\tm: map[string]string{\n\t\t\t\t\"key\": \"value\",\n\t\t\t},\n\t\t\tp: map[string]string{\n\t\t\t\t\"key\": \"value\",\n\t\t\t},\n\t\t\tok: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.ok, LabelsIncludeAny(tt.p, tt.m))\n\t\t})\n\t}\n}\n\nfunc TestMaskString(t *testing.T) {\n\tinput := \"mysecret\"\n\tmasked := MaskString(input)\n\t// decode base64 to compare properties\n\tdecoded, err := base64.StdEncoding.DecodeString(masked)\n\trequire.NoError(t, err)\n\trequire.Len(t, decoded, len(input))\n\t// ensure it is not equal to input\n\trequire.NotEqual(t, input, string(decoded))\n\t// ensure all runes are from anonymizePattern\n\tallowed := make(map[rune]struct{})\n\tfor _, r := range anonymizePattern {\n\t\tallowed[r] = struct{}{}\n\t}\n\tfor _, r := range string(decoded) {\n\t\t_, ok := allowed[r]\n\t\trequire.True(t, ok, \"unexpected rune: %q\", r)\n\t}\n}\n\nfunc TestNewHeaders(t *testing.T) {\n\tinput := []string{\n\t\t\"X-Test: foo\",\n\t\t\"X-Test: bar\",\n\t\t\"Content-Type: application/json\",\n\t\t\"InvalidHeader\", // should be ignored\n\t}\n\ths := NewHeaders(input)\n\t// flatten to a map for easier assertions\n\tgot := map[string][]string{}\n\tfor _, h := range hs {\n\t\tfor k, v := range h {\n\t\t\tgot[k] = append(got[k], v...)\n\t\t}\n\t}\n\t// expected values\n\trequire.Contains(t, got, \"X-Test\")\n\trequire.Contains(t, got, \"Content-Type\")\n\t// order of values is not guaranteed\n\trequire.ElementsMatch(t, []string{\"foo\", \"bar\"}, got[\"X-Test\"])\n\trequire.ElementsMatch(t, []string{\"application/json\"}, got[\"Content-Type\"])\n}\n\nfunc TestLabelStrToSelector(t *testing.T) {\n\t// empty case returns nil\n\trequire.Nil(t, LabelStrToSelector(\"\"))\n\n\tsel := LabelStrToSelector(\"key=value,foo=bar\")\n\trequire.NotNil(t, sel)\n\n\t// matches exact set\n\tm := map[string]string{\"key\": \"value\", \"foo\": \"bar\"}\n\trequire.True(t, sel.Matches(labels.Set(m)))\n\n\t// does not match different values\n\tm2 := map[string]string{\"key\": \"other\", \"foo\": \"bar\"}\n\trequire.False(t, sel.Matches(labels.Set(m2)))\n}\n\nfunc TestCaptureOutput(t *testing.T) {\n\tout := CaptureOutput(func() {\n\t\tfmt.Print(\"hello world\")\n\t})\n\trequire.Equal(t, \"hello world\", out)\n}\n\nfunc TestContains(t *testing.T) {\n\trequire.True(t, Contains(\"abcdef\", \"bcd\"))\n\trequire.False(t, Contains(\"abcdef\", \"xyz\"))\n}\n"
  },
  {
    "path": "release-please-config.json",
    "content": "{\n  \"packages\": {\n    \".\": {\n      \"changelog-path\": \"CHANGELOG.md\",\n      \"release-type\": \"go\",\n      \"prerelease\": false,\n      \"bump-minor-pre-major\": true,\n      \"bump-patch-for-minor-pre-major\": true,\n      \"draft\": false,\n      \"extra-files\": [\n        \"README.md\",\n        \"deploy/manifest.yaml\",\n        \"chart/Chart.yaml\",\n        \"chart/values.yaml\",\n        \"container/manifests/deployment.yaml\"\n      ],\n      \"changelog-sections\": [\n        {\n          \"type\": \"feat\",\n          \"section\": \"Features\"\n        },\n        {\n          \"type\": \"fix\",\n          \"section\": \"Bug Fixes\"\n        },\n        {\n          \"type\": \"chore\",\n          \"section\": \"Other\"\n        },\n        {\n          \"type\": \"docs\",\n          \"section\": \"Docs\"\n        },\n        {\n          \"type\": \"perf\",\n          \"section\": \"Performance\"\n        },\n        {\n          \"type\": \"build\",\n          \"hidden\": true,\n          \"section\": \"Build\"\n        },\n        {\n          \"type\": \"deps\",\n          \"section\": \"Dependency Updates\"\n        },\n        {\n          \"type\": \"ci\",\n          \"hidden\": true,\n          \"section\": \"CI\"\n        },\n        {\n          \"type\": \"refactor\",\n          \"section\": \"Refactoring\"\n        },\n        {\n          \"type\": \"revert\",\n          \"hidden\": true,\n          \"section\": \"Reverts\"\n        },\n        {\n          \"type\": \"style\",\n          \"hidden\": true,\n          \"section\": \"Styling\"\n        },\n        {\n          \"type\": \"test\",\n          \"hidden\": true,\n          \"section\": \"Tests\"\n        }\n      ]\n    }\n  },\n  \"$schema\": \"https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json\"\n}"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\",\n    \"helpers:pinGitHubActionDigests\",\n    \":gitSignOff\",\n    \"group:allNonMajor\"\n  ],\n  \"addLabels\": [\"dependencies\"],\n  \"postUpdateOptions\": [\n    \"gomodTidy\",\n    \"gomodMassage\"\n  ],\n  \"automerge\": true,\n  \"automergeType\": \"pr\",\n  \"schedule\": [\n    \"at any time\"\n  ],\n  \"platformAutomerge\": true,\n  \"packageRules\": [\n    {\n      \"matchPackageNames\": [\"azure-sdk-for-go\"],\n      \"enabled\": true,\n      \"groupName\": \"azure-group\"\n    },\n    {\n      \"matchPackageNames\": [\"prometheus\"],\n      \"enabled\": true,\n      \"groupName\": \"prometheus-group\"\n    },\n    {\n      \"matchPackageNames\": [\"k8s.io\", \"sigs.k8s.io\"],\n      \"enabled\": true,\n      \"groupName\": \"kubernetes-group\"\n    },\n    {\n      \"matchPackageNames\": [\"golang\"],\n      \"enabled\": true,\n      \"groupName\": \"golang-group\"\n    },\n    {\n      \"matchUpdateTypes\": [\"minor\", \"patch\"],\n      \"matchCurrentVersion\": \"!/^0/\",\n      \"automerge\": true\n    },\n    {\n      \"matchManagers\": [\"gomod\"],\n      \"addLabels\": [\"go\"]\n    },\n    {\n      \"matchManagers\": [\"github-actions\"],\n      \"addLabels\": [\"github_actions\"]\n    },\n    {\n      \"matchManagers\": [\"dockerfile\"],\n      \"addLabels\": [\"docker\"]\n    }\n  ],\n  \"regexManagers\": [\n    {\n      \"fileMatch\": [\n        \"(^|\\\\/)Makefile$\",\n        \"(^|\\\\/)Dockerfile\",\n        \"(^|\\\\/).*\\\\.ya?ml$\",\n        \"(^|\\\\/).*\\\\.toml$\",\n        \"(^|\\\\/).*\\\\.sh$\"\n      ],\n      \"matchStrings\": [\n        \"# renovate: datasource=(?<datasource>.+?) depName=(?<depName>.+?)\\\\s.*?_VERSION ?(\\\\??=|\\\\: ?) ?\\\\\\\"?(?<currentValue>.+?)?\\\\\\\"?\\\\s\"\n      ]\n    }\n  ]\n}\n"
  }
]