The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    ├── CODEOWNERS
    ├── pull_request_template.md
    ├── settings.yml
    └── workflows
    │   ├── build_container.yaml
    │   ├── golangci_lint.yaml
    │   ├── release.yaml
    │   ├── semantic_pr.yaml
    │   └── test.yaml
├── .gitignore
├── .goreleaser.yaml
├── .krew.yaml
├── .release-please-manifest.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── SUPPORTED_MODELS.md
├── charts
    └── k8sgpt
    │   ├── Chart.yaml
    │   ├── templates
    │       ├── _helpers.tpl
    │       ├── deployment.yaml
    │       ├── role.yaml
    │       ├── rolebinding.yaml
    │       ├── sa.yaml
    │       ├── secret.yaml
    │       ├── service.yaml
    │       └── serviceMonitor.yaml
    │   └── values.yaml
├── cmd
    ├── analyze
    │   └── analyze.go
    ├── auth
    │   ├── add.go
    │   ├── auth.go
    │   ├── default.go
    │   ├── list.go
    │   ├── remove.go
    │   └── update.go
    ├── cache
    │   ├── add.go
    │   ├── cache.go
    │   ├── get.go
    │   ├── list.go
    │   ├── purge.go
    │   └── remove.go
    ├── customAnalyzer
    │   ├── add.go
    │   ├── customAnalyzer.go
    │   ├── list.go
    │   └── remove.go
    ├── dump
    │   └── dump.go
    ├── filters
    │   ├── add.go
    │   ├── filters.go
    │   ├── list.go
    │   └── remove.go
    ├── generate
    │   └── generate.go
    ├── integration
    │   ├── activate.go
    │   ├── deactivate.go
    │   ├── integration.go
    │   └── list.go
    ├── root.go
    ├── root_test.go
    ├── serve
    │   └── serve.go
    └── version.go
├── container
    ├── Dockerfile
    └── dashboards
    │   └── k8sgpt-dashboard.json
├── go.mod
├── go.sum
├── images
    ├── banner-black.png
    ├── banner-white.png
    ├── banner-white.svg
    ├── demo.gif
    ├── demo1.gif
    ├── demo2.gif
    ├── demo3.png
    ├── demo4.gif
    ├── demo5.gif
    ├── image.png
    ├── landing.png
    └── nodes.gif
├── main.go
├── pkg
    ├── ai
    │   ├── amazonbedrock.go
    │   ├── amazonbedrock_mock_test.go
    │   ├── amazonbedrock_test.go
    │   ├── amazonsagemaker.go
    │   ├── azureopenai.go
    │   ├── bedrock_interfaces.go
    │   ├── bedrock_support
    │   │   ├── completions.go
    │   │   ├── completions_test.go
    │   │   ├── model.go
    │   │   ├── model_test.go
    │   │   ├── responses.go
    │   │   └── responses_test.go
    │   ├── cohere.go
    │   ├── customrest.go
    │   ├── factory.go
    │   ├── googlegenai.go
    │   ├── googlevertexai.go
    │   ├── huggingface.go
    │   ├── iai.go
    │   ├── interactive
    │   │   └── interactive.go
    │   ├── localai.go
    │   ├── noopai.go
    │   ├── ocigenai.go
    │   ├── ollama.go
    │   ├── openai.go
    │   ├── openai_header_transport_test.go
    │   ├── prompts.go
    │   └── watsonxai.go
    ├── analysis
    │   ├── analysis.go
    │   ├── analysis_test.go
    │   ├── output.go
    │   └── output_test.go
    ├── analyzer
    │   ├── analyzer.go
    │   ├── configmap.go
    │   ├── configmap_test.go
    │   ├── cronjob.go
    │   ├── cronjob_test.go
    │   ├── deployment.go
    │   ├── deployment_test.go
    │   ├── events_test.go
    │   ├── gateway.go
    │   ├── gateway_test.go
    │   ├── gatewayclass.go
    │   ├── gatewayclass_test.go
    │   ├── hpa.go
    │   ├── hpa_test.go
    │   ├── httproute.go
    │   ├── httproute_test.go
    │   ├── ingress.go
    │   ├── ingress_test.go
    │   ├── job.go
    │   ├── job_test.go
    │   ├── log.go
    │   ├── log_test.go
    │   ├── mutating_webhook.go
    │   ├── mutating_webhook_test.go
    │   ├── netpol.go
    │   ├── netpol_test.go
    │   ├── node.go
    │   ├── node_test.go
    │   ├── pdb.go
    │   ├── pdb_test.go
    │   ├── pod.go
    │   ├── pod_test.go
    │   ├── pvc.go
    │   ├── pvc_test.go
    │   ├── rs.go
    │   ├── rs_test.go
    │   ├── security.go
    │   ├── security_test.go
    │   ├── service.go
    │   ├── service_test.go
    │   ├── statefulset.go
    │   ├── statefulset_test.go
    │   ├── storage.go
    │   ├── storage_test.go
    │   ├── test_utils.go
    │   ├── validating_webhook.go
    │   └── validating_webhook_test.go
    ├── cache
    │   ├── azuresa_based.go
    │   ├── cache.go
    │   ├── file_based.go
    │   ├── gcs_based.go
    │   ├── interplex_based.go
    │   ├── interplex_based_test.go
    │   ├── s3_based.go
    │   └── types.go
    ├── common
    │   └── types.go
    ├── custom
    │   ├── client.go
    │   └── types.go
    ├── custom_analyzer
    │   └── customAnalyzer.go
    ├── integration
    │   ├── aws
    │   │   ├── aws.go
    │   │   └── eks.go
    │   ├── integration.go
    │   ├── integration_test.go
    │   ├── keda
    │   │   ├── keda.go
    │   │   └── scaledobject_analyzer.go
    │   ├── kyverno
    │   │   ├── analyzer.go
    │   │   ├── analyzer_test.go
    │   │   └── kyverno.go
    │   └── prometheus
    │   │   ├── config_analyzer.go
    │   │   ├── prometheus.go
    │   │   └── relabel_analyzer.go
    ├── kubernetes
    │   ├── apireference.go
    │   ├── apireference_test.go
    │   ├── kubernetes.go
    │   └── types.go
    ├── server
    │   ├── README.md
    │   ├── analyze
    │   │   ├── analyze.go
    │   │   └── handler.go
    │   ├── client_example
    │   │   ├── README.md
    │   │   └── main.go
    │   ├── config
    │   │   ├── config.go
    │   │   ├── handler.go
    │   │   └── integration.go
    │   ├── example
    │   │   └── main.go
    │   ├── log.go
    │   ├── mcp.go
    │   ├── query
    │   │   ├── handler.go
    │   │   ├── query.go
    │   │   └── query_test.go
    │   ├── server.go
    │   └── server_test.go
    └── util
    │   ├── util.go
    │   └── util_test.go
├── release-please-config.json
└── renovate.json


/.github/CODEOWNERS:
--------------------------------------------------------------------------------
 1 | # CODEOWNERS file indicates code owners for certain files
 2 | #
 3 | # Code owners will automatically be added as a reviewer for PRs that touch
 4 | # the owned files.
 5 | #
 6 | 
 7 | # Default owners for everything in the repo
 8 | #
 9 | # Unless a later match takes precedence, these owners will be requested for
10 | # review when someone opens a pull request.
11 | 
12 | /.github/settings.yml @k8sgpt-ai/maintainers
13 | * @k8sgpt-ai/maintainers @k8sgpt-ai/k8sgpt-maintainers @k8sgpt-ai/k8sgpt-approvers
14 | 


--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
 1 | <!-- 
 2 | Thanks for creating this pull request 🤗
 3 | 
 4 | Please 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.
 5 | -->
 6 | 
 7 | <!-- If this pull request closes an issue, please mention the issue number below -->
 8 | Closes # <!-- Issue # here -->
 9 | 
10 | ## 📑 Description
11 | <!-- Add a brief description of the pr -->
12 | 
13 | ## ✅ Checks
14 | <!-- Make sure your pr passes the CI checks and do check the following fields as needed - -->
15 | - [ ] My pull request adheres to the code style of this project
16 | - [ ] My code requires changes to the documentation
17 | - [ ] I have updated the documentation as required
18 | - [ ] All the tests have passed
19 | 
20 | ## ℹ Additional Information
21 | <!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->


--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
 1 | repository:
 2 |   name: "k8sgpt"
 3 |   description: "Giving Kubernetes SRE superpowers to everyone"
 4 |   homepage_url: "https://k8sgpt.ai"
 5 |   topics: kubernetes, devops, tooling, openai, sre
 6 | 
 7 |   default_branch: main
 8 |   allow_squash_merge: true
 9 |   allow_merge_commit: true
10 |   allow_rebase_merge: true
11 | 
12 |   has_wiki: false
13 | 
14 |   teams:
15 |     - name: "maintainers"
16 |       permission: "admin"
17 |     - name: "k8sgpt-maintainers"
18 |       permission: "maintain"
19 |     - name: "k8sgpt-approvers"
20 |       permission: "push"
21 |     - name: "contributors"
22 |       permission: "push"
23 | 
24 | branches:
25 |   - name: main
26 |     protection:
27 |       required_pull_request_reviews:
28 |         required_approving_review_count: 1
29 |         dismiss_stale_reviews: true
30 |         require_code_owner_reviews: true
31 |         dismissal_restrictions: {}
32 |         code_owner_approval: true
33 |         required_conversation_resolution: true
34 | 
35 |       required_status_checks:
36 |         strict: true
37 |         contexts:
38 |           - "DCO"
39 | 
40 |       enforce_admins: true
41 | 
42 |       required_linear_history: true
43 | 
44 |       restrictions:
45 |         users: []
46 |         apps: []
47 |         teams: []


--------------------------------------------------------------------------------
/.github/workflows/golangci_lint.yaml:
--------------------------------------------------------------------------------
 1 | name: Run golangci-lint
 2 | 
 3 | on:
 4 |   pull_request:
 5 |     branches: [main]
 6 | 
 7 | jobs:
 8 |   golangci-lint:
 9 |     runs-on: ubuntu-latest
10 |     steps:
11 |       - name: Check out code into the Go module directory
12 |         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
13 | 
14 |       - name: golangci-lint
15 |         uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
16 |         with:
17 |           version: v2.1.0
18 |           only-new-issues: true


--------------------------------------------------------------------------------
/.github/workflows/semantic_pr.yaml:
--------------------------------------------------------------------------------
 1 | name: Semantic PR Validation
 2 | on:
 3 |   pull_request_target:
 4 |     types:
 5 |       - opened
 6 |       - edited
 7 |       - synchronize
 8 | defaults:
 9 |   run:
10 |     shell: bash
11 | jobs:
12 |   validate:
13 |     runs-on: ubuntu-latest
14 |     permissions:
15 |       contents: read # Needed for checking out the repository
16 |       pull-requests: read # Needed for reading prs
17 |     steps:
18 |       - name: Validate Pull Request
19 |         uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
20 |         env:
21 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 |         with:
23 |           # Configure which types are allowed.
24 |           # Default: https://github.com/commitizen/conventional-commit-types
25 |           types: |
26 |             feat
27 |             fix
28 |             build
29 |             chore
30 |             ci
31 |             docs
32 |             perf
33 |             refactor
34 |             revert
35 |             style
36 |             test
37 |             deps
38 |           scopes: |
39 |             deps
40 |           # Configure that a scope must always be provided.
41 |           requireScope: false
42 |           # When using "Squash and merge" on a PR with only one commit, GitHub
43 |           # will suggest using that commit message instead of the PR title for the
44 |           # merge commit, and it's easy to commit this by mistake. Enable this option
45 |           # to also validate the commit message for one commit PRs.
46 |           validateSingleCommit: true
47 |           # Configure additional validation for the subject based on a regex.
48 |           # This ensures the subject doesn't start with an uppercase character.
49 |           subjectPattern: ^(?![A-Z]).+$
50 |           # If `subjectPattern` is configured, you can use this property to override
51 |           # the default error message that is shown when the pattern doesn't match.
52 |           # The variables `subject` and `title` can be used within the message.
53 |           subjectPatternError: |
54 |             The subject "{subject}" found in the pull request title "{title}"
55 |             didn't match the configured pattern. Please ensure that the subject
56 |             doesn't start with an uppercase character.


--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
 1 | name: Run tests
 2 | 
 3 | on:
 4 |   push:
 5 |     branches:
 6 |       - main
 7 |   pull_request:
 8 |     branches:
 9 |       - main
10 | 
11 | env:
12 |   GO_VERSION: "~1.22"
13 | 
14 | jobs:
15 |   build:
16 |     runs-on: ubuntu-latest
17 |     steps:
18 |       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
19 | 
20 |       - name: Set up Go
21 |         uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
22 |         with:
23 |           go-version: ${{ env.GO_VERSION }}
24 | 
25 |       - name: Run test
26 |         run: go test ./... -coverprofile=coverage.txt
27 |       - name: Upload coverage to Codecov
28 |         uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
29 |         env:
30 |           CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
31 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | .idea
 2 | __debug*
 3 | .DS_Store
 4 | k8sgpt*
 5 | !charts/k8sgpt
 6 | *.vscode
 7 | dist/
 8 | 
 9 | bin/
10 | pkg/server/example/example


--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | # This is an example .goreleaser.yml file with some sensible defaults.
 3 | # Make sure to check the documentation at https://goreleaser.com
 4 | before:
 5 |   hooks:
 6 |     # You may remove this if you don't use go modules.
 7 |     - go mod tidy
 8 |     # you may remove this if you don't need go generate
 9 |     - go generate ./...
10 | builds:
11 |   - env:
12 |       - CGO_ENABLED=0
13 |     goos:
14 |       - linux
15 |       - windows
16 |       - darwin
17 |     ldflags:
18 |       - -s -w
19 |       - -X main.version={{.Version}}
20 |       - -X main.commit={{.ShortCommit}}
21 |       - -X main.Date={{.CommitDate}}
22 | 
23 | nfpms:
24 |   - file_name_template: "{{ .ProjectName }}_{{ .Arch }}"
25 |     maintainer: "K8sGPT Maintainers <contact@k8sgpt.ai>"
26 |     homepage: https://k8sgpt.ai
27 |     description: >-
28 |       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.
29 |     license: "Apache-2.0"
30 |     formats:
31 |       - deb
32 |       - rpm
33 |       - apk
34 |     bindir: /usr/bin
35 |     section: utils
36 |     contents:
37 |       - src: ./LICENSE
38 |         dst: /usr/share/doc/k8sgpt/copyright
39 |         file_info:
40 |           mode: 0644
41 | 
42 | sboms:
43 |   - artifacts: archive
44 | 
45 | archives:
46 |   - format: tar.gz
47 |     # this name template makes the OS and Arch compatible with the results of uname.
48 |     name_template: >-
49 |       {{ .ProjectName }}_
50 |       {{- title .Os }}_
51 |       {{- if eq .Arch "amd64" }}x86_64
52 |       {{- else if eq .Arch "386" }}i386
53 |       {{- else }}{{ .Arch }}{{ end }}
54 |       {{- if .Arm }}v{{ .Arm }}{{ end }}
55 |     # use zip for windows archives
56 |     format_overrides:
57 |       - goos: windows
58 |         format: zip
59 | 
60 | brews:
61 |   - name: k8sgpt
62 |     homepage: https://k8sgpt.ai
63 |     repository:
64 |       owner: k8sgpt-ai
65 |       name: homebrew-k8sgpt
66 | 
67 | checksum:
68 |   name_template: "checksums.txt"
69 | 
70 | snapshot:
71 |   name_template: "{{ incpatch .Version }}-next"
72 | 
73 | announce:
74 |   slack:
75 |     # Whether its enabled or not.
76 |     #
77 |     # Templates: allowed (since v2.6).
78 |     enabled: true
79 | 
80 |     # Message template to use while publishing.
81 |     #
82 |     # Default: '{{ .ProjectName }} {{ .Tag }} is out! Check it out at {{ .ReleaseURL }}'.
83 |     # Templates: allowed.
84 |     message_template: "{{ .ProjectName }} release {{.Tag}} is out!"
85 | 
86 |     # The name of the channel that the user selected as a destination for webhook messages.
87 |     channel: "#general"
88 | 
89 |     # Set your Webhook's user name.
90 |     username: "K8sGPT"
91 | 
92 |     # Emoji to use as the icon for this message. Overrides icon_url.
93 |     icon_emoji: ""
94 | 
95 |     # URL to an image to use as the icon for this message.
96 |     icon_url: ""
97 | 
98 | 


--------------------------------------------------------------------------------
/.krew.yaml:
--------------------------------------------------------------------------------
  1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2
  2 | kind: Plugin
  3 | metadata:
  4 |   name: gpt
  5 | spec:
  6 |   version: {{ .TagName }}
  7 |   homepage: https://github.com/k8sgpt-ai/k8sgpt
  8 |   shortDescription: "Giving Kubernetes Superpowers to everyone"
  9 |   description: |
 10 |     A tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
 11 |   platforms:
 12 |     ##########
 13 |     # Darwin #
 14 |     ##########
 15 |     - selector:
 16 |         matchLabels:
 17 |           os: darwin
 18 |           arch: amd64
 19 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_x86_64.tar.gz" .TagName | indent 6 }}
 20 |       files:
 21 |         - from: "k8sgpt"
 22 |           to: "kubectl-gpt"
 23 |         - from: "LICENSE"
 24 |           to: "."
 25 |       bin: kubectl-gpt
 26 |     - selector:
 27 |         matchLabels:
 28 |           os: darwin
 29 |           arch: arm64
 30 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_arm64.tar.gz" .TagName | indent 6 }}
 31 |       files:
 32 |         - from: "k8sgpt"
 33 |           to: "kubectl-gpt"
 34 |         - from: "LICENSE"
 35 |           to: "."
 36 |       bin: kubectl-gpt
 37 | 
 38 |     #########
 39 |     # Linux #
 40 |     #########
 41 |     - selector:
 42 |         matchLabels:
 43 |           os: linux
 44 |           arch: amd64
 45 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_x86_64.tar.gz" .TagName | indent 6 }}
 46 |       files:
 47 |         - from: "k8sgpt"
 48 |           to: "kubectl-gpt"
 49 |         - from: "LICENSE"
 50 |           to: "."
 51 |       bin: kubectl-gpt
 52 |     - selector:
 53 |         matchLabels:
 54 |           os: linux
 55 |           arch: arm64
 56 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_arm64.tar.gz" .TagName | indent 6 }}
 57 |       files:
 58 |         - from: "k8sgpt"
 59 |           to: "kubectl-gpt"
 60 |         - from: "LICENSE"
 61 |           to: "."
 62 |       bin: kubectl-gpt
 63 |     - selector:
 64 |         matchLabels:
 65 |           os: linux
 66 |           arch: "386"
 67 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_i386.tar.gz" .TagName | indent 6 }}
 68 |       files:
 69 |         - from: "k8sgpt"
 70 |           to: "kubectl-gpt"
 71 |         - from: "LICENSE"
 72 |           to: "."
 73 |       bin: kubectl-gpt
 74 | 
 75 |     ###########
 76 |     # Windows #
 77 |     ###########
 78 |     - selector:
 79 |         matchLabels:
 80 |           os: windows
 81 |           arch: amd64
 82 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_x86_64.zip" .TagName | indent 6 }}
 83 |       files:
 84 |         - from: "k8sgpt"
 85 |           to: "kubectl-gpt"
 86 |         - from: "LICENSE"
 87 |           to: "."
 88 |       bin: kubectl-gpt
 89 |     - selector:
 90 |         matchLabels:
 91 |           os: windows
 92 |           arch: arm64
 93 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_arm64.zip" .TagName | indent 6 }}
 94 |       files:
 95 |         - from: "k8sgpt"
 96 |           to: "kubectl-gpt"
 97 |         - from: "LICENSE"
 98 |           to: "."
 99 |       bin: kubectl-gpt
100 |     - selector:
101 |         matchLabels:
102 |           os: windows
103 |           arch: "386"
104 |       {{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_i386.zip" .TagName | indent 6 }}
105 |       files:
106 |         - from: "k8sgpt"
107 |           to: "kubectl-gpt"
108 |         - from: "LICENSE"
109 |           to: "."
110 |       bin: kubectl-gpt
111 | 


--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {".":"0.4.21"}


--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
 1 | # Security Policy
 2 | 
 3 | ## Supported Versions
 4 | 
 5 | We currently support the latest release for security patching and will deploy forward releases.
 6 | 
 7 | For 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`
 8 | 
 9 | ## Reporting a Vulnerability
10 | 
11 | If 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.
12 | 


--------------------------------------------------------------------------------
/SUPPORTED_MODELS.md:
--------------------------------------------------------------------------------
 1 | # Supported AI Providers and Models in K8sGPT
 2 | 
 3 | K8sGPT 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.
 4 | 
 5 | ---
 6 | 
 7 | ## Providers and Supported Models
 8 | 
 9 | ### OpenAI
10 | - **Model:** User-configurable (any model supported by OpenAI, e.g., `gpt-3.5-turbo`, `gpt-4`, etc.)
11 | 
12 | ### Azure OpenAI
13 | - **Model:** User-configurable (any model deployed in your Azure OpenAI resource)
14 | 
15 | ### LocalAI
16 | - **Model:** User-configurable (default: `llama3`)
17 | 
18 | ### Ollama
19 | - **Model:** User-configurable (default: `llama3`, others can be specified)
20 | 
21 | ### NoOpAI
22 | - **Model:** N/A (no real model, used for testing)
23 | 
24 | ### Cohere
25 | - **Model:** User-configurable (any model supported by Cohere)
26 | 
27 | ### Amazon Bedrock
28 | - **Supported Models:**
29 |   - anthropic.claude-sonnet-4-20250514-v1:0
30 |   - us.anthropic.claude-sonnet-4-20250514-v1:0
31 |   - eu.anthropic.claude-sonnet-4-20250514-v1:0
32 |   - apac.anthropic.claude-sonnet-4-20250514-v1:0
33 |   - us.anthropic.claude-3-7-sonnet-20250219-v1:0
34 |   - eu.anthropic.claude-3-7-sonnet-20250219-v1:0
35 |   - apac.anthropic.claude-3-7-sonnet-20250219-v1:0
36 |   - anthropic.claude-3-5-sonnet-20240620-v1:0
37 |   - us.anthropic.claude-3-5-sonnet-20241022-v2:0
38 |   - anthropic.claude-v2
39 |   - anthropic.claude-v1
40 |   - anthropic.claude-instant-v1
41 |   - ai21.j2-ultra-v1
42 |   - ai21.j2-jumbo-instruct
43 |   - amazon.titan-text-express-v1
44 |   - amazon.nova-pro-v1:0
45 |   - eu.amazon.nova-pro-v1:0
46 |   - us.amazon.nova-pro-v1:0
47 |   - amazon.nova-lite-v1:0
48 |   - eu.amazon.nova-lite-v1:0
49 |   - us.amazon.nova-lite-v1:0
50 |   - anthropic.claude-3-haiku-20240307-v1:0
51 | 
52 | ### Amazon SageMaker
53 | - **Model:** User-configurable (any model deployed in your SageMaker endpoint)
54 | 
55 | ### Google GenAI
56 | - **Model:** User-configurable (any model supported by Google GenAI, e.g., `gemini-pro`)
57 | 
58 | ### Huggingface
59 | - **Model:** User-configurable (any model supported by Huggingface Inference API)
60 | 
61 | ### Google VertexAI
62 | - **Supported Models:**
63 |   - gemini-1.0-pro-001
64 | 
65 | ### OCI GenAI
66 | - **Model:** User-configurable (any model supported by OCI GenAI)
67 | 
68 | ### Custom REST
69 | - **Model:** User-configurable (any model your custom REST endpoint supports)
70 | 
71 | ### IBM Watsonx
72 | - **Supported Models:**
73 |   - ibm/granite-13b-chat-v2
74 | 
75 | ---
76 | 
77 | For more details on configuring each provider and model, refer to the official K8sGPT documentation and the provider's own documentation. 


--------------------------------------------------------------------------------
/charts/k8sgpt/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | appVersion: v0.3.0 #x-release-please-version
3 | description: A Helm chart for K8SGPT
4 | name: k8sgpt
5 | type: application
6 | version: 1.0.0
7 | 


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/_helpers.tpl:
--------------------------------------------------------------------------------
 1 | {{/*
 2 | Expand the name of the chart.
 3 | */}}
 4 | {{- define "k8sgpt.name" -}}
 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
 6 | {{- end }}
 7 | 
 8 | {{/*
 9 | Create a default fully qualified app name.
10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11 | If release name contains chart name it will be used as a full name.
12 | */}}
13 | {{- define "k8sgpt.fullname" -}}
14 | {{- if .Values.fullnameOverride }}
15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
16 | {{- else }}
17 | {{- $name := default .Chart.Name .Values.nameOverride }}
18 | {{- if contains $name .Release.Name }}
19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }}
20 | {{- else }}
21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
22 | {{- end }}
23 | {{- end }}
24 | {{- end }}
25 | 
26 | {{/*
27 | Create chart name and version as used by the chart label.
28 | */}}
29 | {{- define "k8sgpt.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
31 | {{- end }}
32 | 
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "k8sgpt.labels" -}}
37 | helm.sh/chart: {{ include "k8sgpt.chart" . }}
38 | app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
39 | app.kubernetes.io/instance: {{ .Release.Name }}
40 | app.kubernetes.io/managed-by: {{ .Release.Service }}
41 | {{- if .Chart.AppVersion }}
42 | app.kubernetes.io/version: {{ .Chart.AppVersion }}
43 | {{- end }}
44 | {{- end }}


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/deployment.yaml:
--------------------------------------------------------------------------------
 1 | apiVersion: apps/v1
 2 | kind: Deployment
 3 | metadata:
 4 |   name: {{ template "k8sgpt.fullname" . }}
 5 |   namespace: {{ .Release.Namespace | quote }}
 6 |   {{- if .Values.deployment.annotations }}
 7 |   annotations:
 8 |   {{- toYaml .Values.deployment.annotations | nindent 4 }}
 9 |   {{- end }}
10 |   labels:
11 |     {{- include "k8sgpt.labels" . | nindent 4 }}
12 | spec:
13 |   replicas: 1
14 |   selector:
15 |     matchLabels:
16 |       app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
17 |       app.kubernetes.io/instance: {{ .Release.Name }}
18 |   template:
19 |     metadata:
20 |       labels:
21 |         app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
22 |         app.kubernetes.io/instance: {{ .Release.Name }}
23 |     spec:
24 |       {{- if .Values.deployment.securityContext }}
25 |       securityContext:
26 |         {{- toYaml .Values.deployment.securityContext | nindent 8 }}
27 |       {{- end }}
28 |       serviceAccountName: {{ template "k8sgpt.fullname" . }}
29 |       containers:
30 |       - name: k8sgpt-container
31 |         imagePullPolicy: {{ .Values.deployment.imagePullPolicy }}
32 |         image: {{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion }}
33 |         ports:
34 |         - containerPort: 8080
35 |         args: ["serve"]
36 |         {{- if .Values.deployment.resources }}
37 |         resources:
38 |         {{- toYaml .Values.deployment.resources | nindent 10 }}
39 |         {{- end }}
40 |         env:
41 |         - name: K8SGPT_MODEL
42 |           value: {{ .Values.deployment.env.model }}
43 |         - name: K8SGPT_BACKEND
44 |           value: {{ .Values.deployment.env.backend }}
45 |         {{- if .Values.secret.secretKey }}
46 |         - name: K8SGPT_PASSWORD
47 |           valueFrom:
48 |             secretKeyRef:
49 |               name: ai-backend-secret
50 |               key: secret-key
51 |         {{- end }}
52 |         - name: XDG_CONFIG_HOME
53 |           value: /k8sgpt-config/
54 |         - name: XDG_CACHE_HOME
55 |           value: /k8sgpt-config/
56 |         volumeMounts:
57 |         - mountPath: /k8sgpt-config
58 |           name: config
59 |       volumes:
60 |       - emptyDir: {}
61 |         name: config
62 | 


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/role.yaml:
--------------------------------------------------------------------------------
 1 | apiVersion: rbac.authorization.k8s.io/v1
 2 | kind: ClusterRole
 3 | metadata:
 4 |   name: {{ template "k8sgpt.fullname" . }}
 5 |   namespace: {{ .Release.Namespace | quote }}
 6 |   labels:
 7 |     {{- include "k8sgpt.labels" . | nindent 4 }}
 8 | rules:
 9 | - apiGroups:
10 |   - '*'
11 |   resources:
12 |   - '*'
13 |   verbs:
14 |   - get
15 |   - list
16 |   - watch


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/rolebinding.yaml:
--------------------------------------------------------------------------------
 1 | apiVersion: rbac.authorization.k8s.io/v1
 2 | kind: ClusterRoleBinding
 3 | metadata:
 4 |   name: {{ template "k8sgpt.fullname" . }}
 5 |   namespace: {{ .Release.Namespace | quote }}
 6 |   labels:
 7 |     {{- include "k8sgpt.labels" . | nindent 4 }}
 8 | subjects:
 9 | - kind: ServiceAccount
10 |   name: {{ template "k8sgpt.fullname" . }}
11 |   namespace: {{ .Release.Namespace | quote }}
12 | roleRef:
13 |   kind: ClusterRole
14 |   name: {{ template "k8sgpt.fullname" . }}
15 |   apiGroup: rbac.authorization.k8s.io


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/sa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 |   name: {{ template "k8sgpt.fullname" . }}
5 |   namespace: {{ .Release.Namespace | quote }}
6 |   labels:
7 |     {{- include "k8sgpt.labels" . | nindent 4 }}


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/secret.yaml:
--------------------------------------------------------------------------------
 1 | {{- if .Values.secret.secretKey }}
 2 | apiVersion: v1
 3 | data:
 4 |   secret-key: {{ .Values.secret.secretKey }}
 5 | kind: Secret
 6 | metadata:
 7 |   name: ai-backend-secret
 8 |   namespace: {{ .Release.Namespace | quote }}
 9 | type: Opaque
10 | {{- end}}


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/service.yaml:
--------------------------------------------------------------------------------
 1 | apiVersion: v1
 2 | kind: Service
 3 | metadata:
 4 |   name: {{ template "k8sgpt.fullname" . }}
 5 |   namespace: {{ .Release.Namespace | quote }}
 6 |   labels:
 7 |     {{- include "k8sgpt.labels" . | nindent 4 }}
 8 |   {{- if .Values.service.annotations }}
 9 |   annotations:
10 |   {{- toYaml .Values.service.annotations | nindent 4 }}
11 |   {{- end }}
12 | spec:
13 |   selector:
14 |     app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
15 |   ports:
16 |     - name: http
17 |       port: 8080
18 |       targetPort: 8080
19 |     - name: metrics
20 |       port: 8081
21 |       targetPort: 8081
22 |   type: {{ .Values.service.type }}
23 | 


--------------------------------------------------------------------------------
/charts/k8sgpt/templates/serviceMonitor.yaml:
--------------------------------------------------------------------------------
 1 | {{- if .Values.serviceMonitor.enabled }}
 2 | apiVersion: monitoring.coreos.com/v1
 3 | kind: ServiceMonitor
 4 | metadata:
 5 |   name: {{ template "k8sgpt.fullname" . }}
 6 |   namespace: {{ .Release.Namespace | quote }}
 7 |   labels:
 8 |     {{- include "k8sgpt.labels" . | nindent 4 }}
 9 |   {{- if .Values.serviceMonitor.additionalLabels }}
10 |     {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }}
11 |   {{- end }}
12 | spec:
13 |   endpoints:
14 |   - honorLabels: true
15 |     path: /metrics
16 |     port: metrics
17 |   selector:
18 |     matchLabels:
19 |       app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
20 |       app.kubernetes.io/instance: {{ .Release.Name }}
21 | {{- end }}


--------------------------------------------------------------------------------
/charts/k8sgpt/values.yaml:
--------------------------------------------------------------------------------
 1 | deployment:
 2 |   image:
 3 |     repository: ghcr.io/k8sgpt-ai/k8sgpt
 4 |     tag: "" # defaults to Chart.appVersion if unspecified
 5 |   imagePullPolicy: Always
 6 |   annotations: {}
 7 |   env:
 8 |     model: "gpt-3.5-turbo"
 9 |     backend: "openai" # one of: [ openai | llama ]
10 |   resources:
11 |     limits:
12 |       cpu: "1"
13 |       memory: "512Mi"
14 |     requests:
15 |       cpu: "0.2"
16 |       memory: "156Mi"
17 |   securityContext: {}
18 |   # Set securityContext.runAsUser/runAsGroup if necessary. Values below were taken from https://github.com/k8sgpt-ai/k8sgpt/blob/main/container/Dockerfile
19 |   # runAsUser: 65532
20 |   # runAsGroup: 65532
21 | secret:
22 |   secretKey: "" # base64 encoded OpenAI token
23 | 
24 | service:
25 |   type: ClusterIP
26 |   annotations: {}
27 | 
28 | serviceMonitor:
29 |   enabled: false
30 |   additionalLabels: {}
31 | 


--------------------------------------------------------------------------------
/cmd/auth/auth.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package auth
15 | 
16 | import (
17 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
18 | 	"github.com/spf13/cobra"
19 | )
20 | 
21 | var (
22 | 	backend        string
23 | 	password       string
24 | 	baseURL        string
25 | 	endpointName   string
26 | 	model          string
27 | 	engine         string
28 | 	temperature    float32
29 | 	providerRegion string
30 | 	providerId     string
31 | 	compartmentId  string
32 | 	topP           float32
33 | 	topK           int32
34 | 	maxTokens      int
35 | 	organizationId string
36 | )
37 | 
38 | var configAI ai.AIConfiguration
39 | 
40 | // authCmd represents the auth command
41 | var AuthCmd = &cobra.Command{
42 | 	Use:   "auth",
43 | 	Short: "Authenticate with your chosen backend",
44 | 	Long:  `Provide the necessary credentials to authenticate with your chosen backend.`,
45 | 	Run: func(cmd *cobra.Command, args []string) {
46 | 		if len(args) == 0 {
47 | 			_ = cmd.Help()
48 | 			return
49 | 		}
50 | 	},
51 | }
52 | 
53 | func init() {
54 | 	// add subcommand to list backends
55 | 	AuthCmd.AddCommand(listCmd)
56 | 	// add subcommand to create new backend provider
57 | 	AuthCmd.AddCommand(addCmd)
58 | 	// add subcommand to remove new backend provider
59 | 	AuthCmd.AddCommand(removeCmd)
60 | 	// add subcommand to set default backend provider
61 | 	AuthCmd.AddCommand(defaultCmd)
62 | 	// add subcommand to update backend provider
63 | 	AuthCmd.AddCommand(updateCmd)
64 | }
65 | 


--------------------------------------------------------------------------------
/cmd/auth/default.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package auth
15 | 
16 | import (
17 | 	"os"
18 | 	"strings"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/spf13/cobra"
22 | 	"github.com/spf13/viper"
23 | )
24 | 
25 | var (
26 | 	providerName string
27 | )
28 | 
29 | var defaultCmd = &cobra.Command{
30 | 	Use:   "default",
31 | 	Short: "Set your default AI backend provider",
32 | 	Long:  "The command to set your new default AI backend provider (default is openai)",
33 | 	Run: func(cmd *cobra.Command, args []string) {
34 | 		err := viper.UnmarshalKey("ai", &configAI)
35 | 		if err != nil {
36 | 			color.Red("Error: %v", err)
37 | 			os.Exit(1)
38 | 		}
39 | 		if providerName == "" {
40 | 			if configAI.DefaultProvider != "" {
41 | 				color.Yellow("Your default provider is %s", configAI.DefaultProvider)
42 | 			} else {
43 | 				color.Yellow("Your default provider is openai")
44 | 			}
45 | 			os.Exit(0)
46 | 		}
47 | 		// lowercase the provider name
48 | 		providerName = strings.ToLower(providerName)
49 | 
50 | 		// Check if the provider is in the provider list
51 | 		providerExists := false
52 | 		for _, provider := range configAI.Providers {
53 | 			if provider.Name == providerName {
54 | 				providerExists = true
55 | 			}
56 | 		}
57 | 		if !providerExists {
58 | 			color.Red("Error: Provider %s does not exist", providerName)
59 | 			os.Exit(1)
60 | 		}
61 | 		// Set the default provider
62 | 		configAI.DefaultProvider = providerName
63 | 
64 | 		viper.Set("ai", configAI)
65 | 		// Viper write config
66 | 		err = viper.WriteConfig()
67 | 		if err != nil {
68 | 			color.Red("Error: %v", err)
69 | 			os.Exit(1)
70 | 		}
71 | 		// Print acknowledgement
72 | 		color.Green("Default provider set to %s", providerName)
73 | 	},
74 | }
75 | 
76 | func init() {
77 | 	// provider name flag
78 | 	defaultCmd.Flags().StringVarP(&providerName, "provider", "p", "", "The name of the provider to set as default")
79 | }
80 | 


--------------------------------------------------------------------------------
/cmd/auth/list.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package auth
15 | 
16 | import (
17 | 	"fmt"
18 | 	"os"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
22 | 	"github.com/spf13/cobra"
23 | 	"github.com/spf13/viper"
24 | )
25 | 
26 | var details bool
27 | 
28 | var listCmd = &cobra.Command{
29 | 	Use:   "list",
30 | 	Short: "List configured providers",
31 | 	Long:  "The list command displays a list of configured providers",
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 
34 | 		// get ai configuration
35 | 		err := viper.UnmarshalKey("ai", &configAI)
36 | 		if err != nil {
37 | 			color.Red("Error: %v", err)
38 | 			os.Exit(1)
39 | 		}
40 | 
41 | 		// Print the default if it is set
42 | 		fmt.Print(color.YellowString("Default: \n"))
43 | 		if configAI.DefaultProvider != "" {
44 | 			fmt.Printf("> %s\n", color.BlueString(configAI.DefaultProvider))
45 | 		} else {
46 | 			fmt.Printf("> %s\n", color.BlueString("openai"))
47 | 		}
48 | 
49 | 		// Get list of all AI Backends and only print them if they are not in the provider list
50 | 		fmt.Print(color.YellowString("Active: \n"))
51 | 		for _, aiBackend := range ai.Backends {
52 | 			providerExists := false
53 | 			for _, provider := range configAI.Providers {
54 | 				if provider.Name == aiBackend {
55 | 					providerExists = true
56 | 				}
57 | 			}
58 | 			if providerExists {
59 | 				fmt.Printf("> %s\n", color.GreenString(aiBackend))
60 | 				if details {
61 | 					for _, provider := range configAI.Providers {
62 | 						if provider.Name == aiBackend {
63 | 							printDetails(provider)
64 | 						}
65 | 					}
66 | 				}
67 | 			}
68 | 		}
69 | 		fmt.Print(color.YellowString("Unused: \n"))
70 | 		for _, aiBackend := range ai.Backends {
71 | 			providerExists := false
72 | 			for _, provider := range configAI.Providers {
73 | 				if provider.Name == aiBackend {
74 | 					providerExists = true
75 | 				}
76 | 			}
77 | 			if !providerExists {
78 | 				fmt.Printf("> %s\n", color.RedString(aiBackend))
79 | 			}
80 | 		}
81 | 	},
82 | }
83 | 
84 | func init() {
85 | 	listCmd.Flags().BoolVar(&details, "details", false, "Print active provider configuration details")
86 | }
87 | 
88 | func printDetails(provider ai.AIProvider) {
89 | 	if provider.Model != "" {
90 | 		fmt.Printf("   - Model: %s\n", provider.Model)
91 | 	}
92 | 	if provider.Engine != "" {
93 | 		fmt.Printf("   - Engine: %s\n", provider.Engine)
94 | 	}
95 | 	if provider.BaseURL != "" {
96 | 		fmt.Printf("   - BaseURL: %s\n", provider.BaseURL)
97 | 	}
98 | }
99 | 


--------------------------------------------------------------------------------
/cmd/auth/remove.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package auth
15 | 
16 | import (
17 | 	"os"
18 | 	"strings"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/spf13/cobra"
22 | 	"github.com/spf13/viper"
23 | )
24 | 
25 | var removeCmd = &cobra.Command{
26 | 	Use:   "remove",
27 | 	Short: "Remove provider(s)",
28 | 	Long:  "The command to remove AI backend provider(s)",
29 | 	PreRun: func(cmd *cobra.Command, args []string) {
30 | 		_ = cmd.MarkFlagRequired("backends")
31 | 	},
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		if backend == "" {
34 | 			color.Red("Error: backends must be set.")
35 | 			_ = cmd.Help()
36 | 			return
37 | 		}
38 | 		inputBackends := strings.Split(backend, ",")
39 | 
40 | 		err := viper.UnmarshalKey("ai", &configAI)
41 | 		if err != nil {
42 | 			color.Red("Error: %v", err)
43 | 			os.Exit(1)
44 | 		}
45 | 
46 | 		for _, b := range inputBackends {
47 | 			foundBackend := false
48 | 			for i, provider := range configAI.Providers {
49 | 				if b == provider.Name {
50 | 					foundBackend = true
51 | 					configAI.Providers = append(configAI.Providers[:i], configAI.Providers[i+1:]...)
52 | 					if configAI.DefaultProvider == b {
53 | 						configAI.DefaultProvider = "openai"
54 | 					}
55 | 					color.Green("%s deleted from the AI backend provider list", b)
56 | 					break
57 | 				}
58 | 			}
59 | 			if !foundBackend {
60 | 				color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", b)
61 | 				os.Exit(1)
62 | 			}
63 | 		}
64 | 
65 | 		viper.Set("ai", configAI)
66 | 		if err := viper.WriteConfig(); err != nil {
67 | 			color.Red("Error writing config file: %s", err.Error())
68 | 			os.Exit(1)
69 | 		}
70 | 
71 | 	},
72 | }
73 | 
74 | func init() {
75 | 	// add flag for backends
76 | 	removeCmd.Flags().StringVarP(&backend, "backends", "b", "", "Backend AI providers to remove (separated by a comma)")
77 | }
78 | 


--------------------------------------------------------------------------------
/cmd/cache/add.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"fmt"
19 | 	"os"
20 | 	"strings"
21 | 
22 | 	"github.com/fatih/color"
23 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
24 | 	"github.com/spf13/cobra"
25 | )
26 | 
27 | var (
28 | 	region string
29 | 	//nolint:unused
30 | 	bucketName     string
31 | 	storageAccount string
32 | 	containerName  string
33 | 	projectId      string
34 | 	endpoint       string
35 | 	insecure       bool
36 | )
37 | 
38 | // addCmd represents the add command
39 | var addCmd = &cobra.Command{
40 | 	Use:   "add [cache type]",
41 | 	Short: "Add a remote cache",
42 | 	Long: `This command allows you to add a remote cache to store the results of an analysis.
43 | 	The supported cache types are:
44 | 	- Azure Blob storage (e.g., k8sgpt cache add azure)
45 | 	- Google Cloud storage (e.g., k8sgpt cache add gcs)
46 | 	- S3 (e.g., k8sgpt cache add s3)
47 | 	- Interplex (e.g., k8sgpt cache add interplex)`,
48 | 	Run: func(cmd *cobra.Command, args []string) {
49 | 		if len(args) == 0 {
50 | 			color.Red("Error: Please provide a value for cache types. Run k8sgpt cache add --help")
51 | 			os.Exit(1)
52 | 		}
53 | 		fmt.Println(color.YellowString("Adding remote based cache"))
54 | 		cacheType := args[0]
55 | 		remoteCache, err := cache.NewCacheProvider(strings.ToLower(cacheType), bucketName, region, endpoint, storageAccount, containerName, projectId, insecure)
56 | 		if err != nil {
57 | 			color.Red("Error: %v", err)
58 | 			os.Exit(1)
59 | 		}
60 | 		err = cache.AddRemoteCache(remoteCache)
61 | 		if err != nil {
62 | 			color.Red("Error: %v", err)
63 | 			os.Exit(1)
64 | 		}
65 | 	},
66 | }
67 | 
68 | func init() {
69 | 	CacheCmd.AddCommand(addCmd)
70 | 	addCmd.Flags().StringVarP(&region, "region", "r", "us-east-1", "The region to use for the AWS S3 or GCS cache")
71 | 	addCmd.Flags().StringVarP(&endpoint, "endpoint", "e", "", "The S3 or minio endpoint")
72 | 	addCmd.Flags().BoolVarP(&insecure, "insecure", "i", false, "Skip TLS verification for S3/Minio custom endpoint")
73 | 	addCmd.Flags().StringVarP(&bucketName, "bucket", "b", "", "The name of the AWS S3 bucket to use for the cache")
74 | 	addCmd.Flags().StringVarP(&projectId, "projectid", "p", "", "The GCP project ID")
75 | 	addCmd.Flags().StringVarP(&storageAccount, "storageacc", "s", "", "The Azure storage account name of the container")
76 | 	addCmd.Flags().StringVarP(&containerName, "container", "c", "", "The Azure container name to use for the cache")
77 | 	addCmd.MarkFlagsRequiredTogether("storageacc", "container")
78 | 	// Tedious check to ensure we don't include arguments from different providers
79 | 	addCmd.MarkFlagsMutuallyExclusive("region", "storageacc")
80 | 	addCmd.MarkFlagsMutuallyExclusive("region", "container")
81 | 	addCmd.MarkFlagsMutuallyExclusive("bucket", "storageacc")
82 | 	addCmd.MarkFlagsMutuallyExclusive("bucket", "container")
83 | 	addCmd.MarkFlagsMutuallyExclusive("projectid", "storageacc")
84 | 	addCmd.MarkFlagsMutuallyExclusive("projectid", "container")
85 | }
86 | 


--------------------------------------------------------------------------------
/cmd/cache/cache.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"github.com/spf13/cobra"
19 | )
20 | 
21 | // cacheCmd represents the cache command
22 | var CacheCmd = &cobra.Command{
23 | 	Use:   "cache",
24 | 	Short: "For working with the cache the results of an analysis",
25 | 	Long:  `Cache commands allow you to add a remote cache, list the contents of the cache, and remove items from the cache.`,
26 | 	Run: func(cmd *cobra.Command, args []string) {
27 | 		err := cmd.Help()
28 | 		if err != nil {
29 | 			panic(err)
30 | 		}
31 | 	},
32 | }
33 | 
34 | func init() {
35 | }
36 | 


--------------------------------------------------------------------------------
/cmd/cache/get.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"fmt"
19 | 	"github.com/fatih/color"
20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
21 | 	"github.com/spf13/cobra"
22 | 	"os"
23 | )
24 | 
25 | // listCmd represents the list command
26 | var getCmd = &cobra.Command{
27 | 	Use:   "get",
28 | 	Short: "Get the current cache",
29 | 	Long:  `Returns the current remote cache being used`,
30 | 	Run: func(cmd *cobra.Command, args []string) {
31 | 
32 | 		// load remote cache if it is configured
33 | 		c, err := cache.GetCacheConfiguration()
34 | 		if err != nil {
35 | 			color.Red("Error: %v", err)
36 | 			os.Exit(1)
37 | 		}
38 | 		fmt.Printf("Current remote cache is: %s", c.GetName())
39 | 	},
40 | }
41 | 
42 | func init() {
43 | 	CacheCmd.AddCommand(getCmd)
44 | 
45 | }
46 | 


--------------------------------------------------------------------------------
/cmd/cache/list.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"os"
19 | 	"reflect"
20 | 
21 | 	"github.com/fatih/color"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
23 | 	"github.com/olekukonko/tablewriter"
24 | 	"github.com/spf13/cobra"
25 | )
26 | 
27 | // listCmd represents the list command
28 | var listCmd = &cobra.Command{
29 | 	Use:   "list",
30 | 	Short: "List the contents of the cache",
31 | 	Long:  `This command allows you to list the contents of the cache.`,
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 
34 | 		// load remote cache if it is configured
35 | 		c, err := cache.GetCacheConfiguration()
36 | 		if err != nil {
37 | 			color.Red("Error: %v", err)
38 | 			os.Exit(1)
39 | 		}
40 | 		names, err := c.List()
41 | 		if err != nil {
42 | 			color.Red("Error: %v", err)
43 | 			os.Exit(1)
44 | 		}
45 | 
46 | 		var headers []string
47 | 		obj := cache.CacheObjectDetails{}
48 | 		objType := reflect.TypeOf(obj)
49 | 		for i := 0; i < objType.NumField(); i++ {
50 | 			field := objType.Field(i)
51 | 			headers = append(headers, field.Name)
52 | 		}
53 | 
54 | 		table := tablewriter.NewWriter(os.Stdout)
55 | 		table.SetHeader(headers)
56 | 
57 | 		for _, v := range names {
58 | 			table.Append([]string{v.Name, v.UpdatedAt.String()})
59 | 		}
60 | 		table.Render()
61 | 	},
62 | }
63 | 
64 | func init() {
65 | 	CacheCmd.AddCommand(listCmd)
66 | 
67 | }
68 | 


--------------------------------------------------------------------------------
/cmd/cache/purge.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"fmt"
19 | 	"os"
20 | 
21 | 	"github.com/fatih/color"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
23 | 	"github.com/spf13/cobra"
24 | )
25 | 
26 | var all bool
27 | 
28 | var purgeCmd = &cobra.Command{
29 | 	Use:   "purge [object name]",
30 | 	Short: "Purge a remote cache",
31 | 	Long:  "This command allows you to delete/purge one object from the cache or all objects with --all flag.",
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		c, err := cache.GetCacheConfiguration()
34 | 		if err != nil {
35 | 			color.Red("Error: %v", err)
36 | 			os.Exit(1)
37 | 		}
38 | 
39 | 		if all {
40 | 			fmt.Println(color.YellowString("Purging all objects from the remote cache."))
41 | 			names, err := c.List()
42 | 			if err != nil {
43 | 				color.Red("Error listing cache objects: %v", err)
44 | 				os.Exit(1)
45 | 			}
46 | 			if len(names) == 0 {
47 | 				fmt.Println(color.GreenString("No objects to delete."))
48 | 				return
49 | 			}
50 | 			var failed []string
51 | 			for _, obj := range names {
52 | 				err := c.Remove(obj.Name)
53 | 				if err != nil {
54 | 					failed = append(failed, obj.Name)
55 | 				}
56 | 			}
57 | 			if len(failed) > 0 {
58 | 				color.Red("Failed to delete: %v", failed)
59 | 				os.Exit(1)
60 | 			}
61 | 			fmt.Println(color.GreenString("All objects deleted."))
62 | 			return
63 | 		}
64 | 
65 | 		if len(args) == 0 {
66 | 			color.Red("Error: Please provide a value for object name or use --all. Run k8sgpt cache purge --help")
67 | 			os.Exit(1)
68 | 		}
69 | 		objectKey := args[0]
70 | 		fmt.Println(color.YellowString("Purging a remote cache."))
71 | 		err = c.Remove(objectKey)
72 | 		if err != nil {
73 | 			color.Red("Error: %v", err)
74 | 			os.Exit(1)
75 | 		}
76 | 		fmt.Println(color.GreenString("Object deleted."))
77 | 	},
78 | }
79 | 
80 | func init() {
81 | 	purgeCmd.Flags().BoolVar(&all, "all", false, "Purge all objects in the cache")
82 | 	CacheCmd.AddCommand(purgeCmd)
83 | }
84 | 


--------------------------------------------------------------------------------
/cmd/cache/remove.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 | 
 7 | 	http://www.apache.org/licenses/LICENSE-2.0
 8 | 
 9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 | */
15 | package cache
16 | 
17 | import (
18 | 	"os"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
22 | 	"github.com/spf13/cobra"
23 | )
24 | 
25 | // removeCmd represents the remove command
26 | var removeCmd = &cobra.Command{
27 | 	Use:   "remove",
28 | 	Short: "Remove the remote cache",
29 | 	Long:  `This command allows you to remove the remote cache and use the default filecache.`,
30 | 	Run: func(cmd *cobra.Command, args []string) {
31 | 
32 | 		err := cache.RemoveRemoteCache()
33 | 		if err != nil {
34 | 			color.Red("Error: %v", err)
35 | 			os.Exit(1)
36 | 		}
37 | 		color.Green("Successfully removed the remote cache")
38 | 	},
39 | }
40 | 
41 | func init() {
42 | 	CacheCmd.AddCommand(removeCmd)
43 | }
44 | 


--------------------------------------------------------------------------------
/cmd/customAnalyzer/add.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package customanalyzer
15 | 
16 | import (
17 | 	"os"
18 | 
19 | 	"github.com/fatih/color"
20 | 	customAnalyzer "github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer"
21 | 	"github.com/spf13/cobra"
22 | 	"github.com/spf13/viper"
23 | )
24 | 
25 | var (
26 | 	name string
27 | 	url  string
28 | 	port int
29 | )
30 | 
31 | var addCmd = &cobra.Command{
32 | 	Use:     "add",
33 | 	Aliases: []string{"add"},
34 | 	Short:   "This command will add a custom analyzer from source",
35 | 	Long:    "This command allows you to add/remote/list an existing custom analyzer.",
36 | 	Run: func(cmd *cobra.Command, args []string) {
37 | 		err := viper.UnmarshalKey("custom_analyzers", &configCustomAnalyzer)
38 | 		if err != nil {
39 | 			color.Red("Error: %v", err)
40 | 			os.Exit(1)
41 | 		}
42 | 		analyzer := customAnalyzer.NewCustomAnalyzer()
43 | 
44 | 		// Check if configuration is valid
45 | 		err = analyzer.Check(configCustomAnalyzer, name, url, port)
46 | 		if err != nil {
47 | 			color.Red("Error adding custom analyzer: %s", err.Error())
48 | 			os.Exit(1)
49 | 		}
50 | 
51 | 		configCustomAnalyzer = append(configCustomAnalyzer, customAnalyzer.CustomAnalyzerConfiguration{
52 | 			Name: name,
53 | 			Connection: customAnalyzer.Connection{
54 | 				Url:  url,
55 | 				Port: port,
56 | 			},
57 | 		})
58 | 
59 | 		viper.Set("custom_analyzers", configCustomAnalyzer)
60 | 		if err := viper.WriteConfig(); err != nil {
61 | 			color.Red("Error writing config file: %s", err.Error())
62 | 			os.Exit(1)
63 | 		}
64 | 		color.Green("%s added to the custom analyzers config list", name)
65 | 
66 | 	},
67 | }
68 | 
69 | func init() {
70 | 	addCmd.Flags().StringVarP(&name, "name", "n", "my-custom-analyzer", "Name of the custom analyzer.")
71 | 	addCmd.Flags().StringVarP(&url, "url", "u", "localhost", "URL for the custom analyzer connection.")
72 | 	addCmd.Flags().IntVarP(&port, "port", "r", 8085, "Port for the custom analyzer connection.")
73 | }
74 | 


--------------------------------------------------------------------------------
/cmd/customAnalyzer/customAnalyzer.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package customanalyzer
15 | 
16 | import (
17 | 	customAnalyzer "github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer"
18 | 	"github.com/spf13/cobra"
19 | )
20 | 
21 | var configCustomAnalyzer []customAnalyzer.CustomAnalyzerConfiguration
22 | 
23 | // authCmd represents the auth command
24 | var CustomAnalyzerCmd = &cobra.Command{
25 | 	Use:   "custom-analyzer",
26 | 	Short: "Manage a custom analyzer",
27 | 	Long:  `This command allows you to manage custom analyzers, including adding, removing, and listing them.`,
28 | 	Run: func(cmd *cobra.Command, args []string) {
29 | 		if len(args) == 0 {
30 | 			_ = cmd.Help()
31 | 			return
32 | 		}
33 | 	},
34 | }
35 | 
36 | func init() {
37 | 	// add subcommand to add custom analyzer
38 | 	CustomAnalyzerCmd.AddCommand(addCmd)
39 | 	// remove subcomment to remove custom analyzer
40 | 	CustomAnalyzerCmd.AddCommand(removeCmd)
41 | 	// list subcomment to list custom analyzer
42 | 	CustomAnalyzerCmd.AddCommand(listCmd)
43 | }
44 | 


--------------------------------------------------------------------------------
/cmd/customAnalyzer/list.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package customanalyzer
15 | 
16 | import (
17 | 	"fmt"
18 | 	"os"
19 | 
20 | 	"github.com/fatih/color"
21 | 	customAnalyzer "github.com/k8sgpt-ai/k8sgpt/pkg/custom_analyzer"
22 | 	"github.com/spf13/cobra"
23 | 	"github.com/spf13/viper"
24 | )
25 | 
26 | var details bool
27 | 
28 | var listCmd = &cobra.Command{
29 | 	Use:   "list",
30 | 	Short: "List configured custom analyzers",
31 | 	Long:  "The list command displays a list of configured custom analyzers",
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 
34 | 		// get custom_analyzers configuration
35 | 		err := viper.UnmarshalKey("custom_analyzers", &configCustomAnalyzer)
36 | 		if err != nil {
37 | 			color.Red("Error: %v", err)
38 | 			os.Exit(1)
39 | 		}
40 | 
41 | 		// Get list of all Custom Analyers configured
42 | 		fmt.Print(color.YellowString("Active: \n"))
43 | 		for _, analyzer := range configCustomAnalyzer {
44 | 			fmt.Printf("> %s\n", color.GreenString(analyzer.Name))
45 | 			if details {
46 | 				printDetails(analyzer)
47 | 			}
48 | 		}
49 | 	},
50 | }
51 | 
52 | func init() {
53 | 	listCmd.Flags().BoolVar(&details, "details", false, "Print custom analyzers configuration details")
54 | }
55 | 
56 | func printDetails(analyzer customAnalyzer.CustomAnalyzerConfiguration) {
57 | 	fmt.Printf("   - Url: %s\n", analyzer.Connection.Url)
58 | 	fmt.Printf("   - Port: %d\n", analyzer.Connection.Port)
59 | 
60 | }
61 | 


--------------------------------------------------------------------------------
/cmd/customAnalyzer/remove.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package customanalyzer
15 | 
16 | import (
17 | 	"os"
18 | 	"strings"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/spf13/cobra"
22 | 	"github.com/spf13/viper"
23 | )
24 | 
25 | var (
26 | 	names string
27 | )
28 | 
29 | var removeCmd = &cobra.Command{
30 | 	Use:   "remove",
31 | 	Short: "Remove custom analyzer(s)",
32 | 	Long:  "The command to remove custom analyzer(s)",
33 | 	PreRun: func(cmd *cobra.Command, args []string) {
34 | 		// Ensure that the "names" flag is provided before running the command
35 | 		_ = cmd.MarkFlagRequired("names")
36 | 	},
37 | 	Run: func(cmd *cobra.Command, args []string) {
38 | 		if names == "" {
39 | 			// Display an error message and show command help if "names" is not set
40 | 			color.Red("Error: names must be set.")
41 | 			_ = cmd.Help()
42 | 			return
43 | 		}
44 | 		// Split the provided names by comma
45 | 		inputCustomAnalyzers := strings.Split(names, ",")
46 | 
47 | 		// Load the custom analyzers from the configuration file
48 | 		err := viper.UnmarshalKey("custom_analyzers", &configCustomAnalyzer)
49 | 		if err != nil {
50 | 			// Display an error message if the configuration cannot be loaded
51 | 			color.Red("Error: %v", err)
52 | 			os.Exit(1)
53 | 		}
54 | 
55 | 		// Iterate over each input analyzer name
56 | 		for _, inputAnalyzer := range inputCustomAnalyzers {
57 | 			foundAnalyzer := false
58 | 			// Search for the analyzer in the current configuration
59 | 			for i, analyzer := range configCustomAnalyzer {
60 | 				if analyzer.Name == inputAnalyzer {
61 | 					foundAnalyzer = true
62 | 
63 | 					// Remove the analyzer from the configuration list
64 | 					configCustomAnalyzer = append(configCustomAnalyzer[:i], configCustomAnalyzer[i+1:]...)
65 | 					color.Green("%s deleted from the custom analyzer list", analyzer.Name)
66 | 					break
67 | 				}
68 | 			}
69 | 			if !foundAnalyzer {
70 | 				// Display an error if the analyzer is not found in the configuration
71 | 				color.Red("Error: %s does not exist in configuration file. Please use k8sgpt custom-analyzer add.", inputAnalyzer)
72 | 				os.Exit(1)
73 | 			}
74 | 		}
75 | 
76 | 		// Save the updated configuration back to the file
77 | 		viper.Set("custom_analyzers", configCustomAnalyzer)
78 | 		if err := viper.WriteConfig(); err != nil {
79 | 			// Display an error if the configuration cannot be written
80 | 			color.Red("Error writing config file: %s", err.Error())
81 | 			os.Exit(1)
82 | 		}
83 | 
84 | 	},
85 | }
86 | 
87 | func init() {
88 | 	// add flag for names
89 | 	removeCmd.Flags().StringVarP(&names, "names", "n", "", "Custom analyzers to remove (separated by a comma)")
90 | }
91 | 


--------------------------------------------------------------------------------
/cmd/dump/dump.go:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright 2023 The K8sGPT Authors.
  3 | Licensed under the Apache License, Version 2.0 (the "License");
  4 | you may not use this file except in compliance with the License.
  5 | You may obtain a copy of the License at
  6 |     http://www.apache.org/licenses/LICENSE-2.0
  7 | Unless required by applicable law or agreed to in writing, software
  8 | distributed under the License is distributed on an "AS IS" BASIS,
  9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 10 | See the License for the specific language governing permissions and
 11 | limitations under the License.
 12 | */
 13 | 
 14 | package dump
 15 | 
 16 | import (
 17 | 	"encoding/json"
 18 | 	"fmt"
 19 | 	"net/http"
 20 | 	"os"
 21 | 	"time"
 22 | 
 23 | 	"github.com/fatih/color"
 24 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
 25 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
 26 | 	"github.com/spf13/cobra"
 27 | 	"github.com/spf13/viper"
 28 | 	"k8s.io/apimachinery/pkg/version"
 29 | )
 30 | 
 31 | type K8sGPTInfo struct {
 32 | 	Version string
 33 | 	Commit  string
 34 | 	Date    string
 35 | }
 36 | type DumpOut struct {
 37 | 	AIConfiguration        ai.AIConfiguration
 38 | 	ActiveFilters          []string
 39 | 	KubenetesServerVersion *version.Info
 40 | 	K8sGPTInfo             K8sGPTInfo
 41 | }
 42 | 
 43 | var DumpCmd = &cobra.Command{
 44 | 	Use:   "dump",
 45 | 	Short: "Creates a dumpfile for debugging issues with K8sGPT",
 46 | 	Long:  `The dump command will create a dump.*.json which will contain K8sGPT non-sensitive configuration information.`,
 47 | 	Run: func(cmd *cobra.Command, args []string) {
 48 | 
 49 | 		// Fetch the configuration object(s)
 50 | 		// get ai configuration
 51 | 		var configAI ai.AIConfiguration
 52 | 		err := viper.UnmarshalKey("ai", &configAI)
 53 | 		if err != nil {
 54 | 			color.Red("Error: %v", err)
 55 | 			os.Exit(1)
 56 | 		}
 57 | 
 58 | 		var newProvider []ai.AIProvider
 59 | 		for _, config := range configAI.Providers {
 60 | 			// we blank out the custom headers for data protection reasons
 61 | 			config.CustomHeaders = make([]http.Header, 0)
 62 | 			// blank out the password
 63 | 			if len(config.Password) > 4 {
 64 | 				config.Password = config.Password[:4] + "***"
 65 | 			} else {
 66 | 				// If the password is shorter than 4 characters
 67 | 				config.Password = "***"
 68 | 			}
 69 | 			newProvider = append(newProvider, config)
 70 | 		}
 71 | 		configAI.Providers = newProvider
 72 | 		activeFilters := viper.GetStringSlice("active_filters")
 73 | 		kubecontext := viper.GetString("kubecontext")
 74 | 		kubeconfig := viper.GetString("kubeconfig")
 75 | 		client, err := kubernetes.NewClient(kubecontext, kubeconfig)
 76 | 		if err != nil {
 77 | 			color.Red("Error: %v", err)
 78 | 			os.Exit(1)
 79 | 		}
 80 | 		v, err := client.Client.Discovery().ServerVersion()
 81 | 		if err != nil {
 82 | 			color.Yellow("Could not find kubernetes server version")
 83 | 		}
 84 | 		var dumpOut DumpOut = DumpOut{
 85 | 			AIConfiguration:        configAI,
 86 | 			ActiveFilters:          activeFilters,
 87 | 			KubenetesServerVersion: v,
 88 | 			K8sGPTInfo: K8sGPTInfo{
 89 | 				Version: viper.GetString("Version"),
 90 | 				Commit:  viper.GetString("Commit"),
 91 | 				Date:    viper.GetString("Date"),
 92 | 			},
 93 | 		}
 94 | 		// Serialize dumpOut to JSON
 95 | 		jsonData, err := json.MarshalIndent(dumpOut, "", " ")
 96 | 		if err != nil {
 97 | 			color.Red("Error: %v", err)
 98 | 			os.Exit(1)
 99 | 		}
100 | 		// Write JSON data to file
101 | 		f := fmt.Sprintf("dump_%s.json", time.Now().Format("20060102150405"))
102 | 		err = os.WriteFile(f, jsonData, 0644)
103 | 		if err != nil {
104 | 			color.Red("Error: %v", err)
105 | 			os.Exit(1)
106 | 		}
107 | 		color.Green("Dump created successfully: %s", f)
108 | 	},
109 | }
110 | 
111 | func init() {
112 | 
113 | }
114 | 


--------------------------------------------------------------------------------
/cmd/filters/add.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package filters
15 | 
16 | import (
17 | 	"os"
18 | 	"strings"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
23 | 	"github.com/spf13/cobra"
24 | 	"github.com/spf13/viper"
25 | )
26 | 
27 | var addCmd = &cobra.Command{
28 | 	Use:   "add [filter(s)]",
29 | 	Short: "Adds one or more new filters.",
30 | 	Long:  `The add command adds one or more new filters to the default set of filters used by the analyze.`,
31 | 	Args:  cobra.ExactArgs(1),
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		inputFilters := strings.Split(args[0], ",")
34 | 		coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
35 | 
36 | 		availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
37 | 		// Verify filter exist
38 | 		invalidFilters := []string{}
39 | 		for _, f := range inputFilters {
40 | 			if f == "" {
41 | 				color.Red("Filter cannot be empty. Please use correct syntax.")
42 | 				os.Exit(1)
43 | 			}
44 | 			foundFilter := false
45 | 			for _, filter := range availableFilters {
46 | 				if filter == f {
47 | 					foundFilter = true
48 | 
49 | 					// WARNING: This is to enable users correctly understand implications
50 | 					// of enabling logs
51 | 					if filter == "Log" {
52 | 						color.Yellow("Warning: by enabling logs, you will be sending potentially sensitive data to the AI backend.")
53 | 					}
54 | 
55 | 					break
56 | 				}
57 | 			}
58 | 			if !foundFilter {
59 | 				invalidFilters = append(invalidFilters, f)
60 | 			}
61 | 		}
62 | 
63 | 		if len(invalidFilters) != 0 {
64 | 			color.Red("Filter %s does not exist. Please use k8sgpt filters list", strings.Join(invalidFilters, ", "))
65 | 			os.Exit(1)
66 | 		}
67 | 
68 | 		// Get defined active_filters
69 | 		activeFilters := viper.GetStringSlice("active_filters")
70 | 		if len(activeFilters) == 0 {
71 | 			activeFilters = coreFilters
72 | 		}
73 | 
74 | 		mergedFilters := append(activeFilters, inputFilters...)
75 | 
76 | 		uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
77 | 
78 | 		// Verify dupplicate
79 | 		if len(dupplicatedFilters) != 0 {
80 | 			color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
81 | 			os.Exit(1)
82 | 		}
83 | 
84 | 		viper.Set("active_filters", uniqueFilters)
85 | 
86 | 		if err := viper.WriteConfig(); err != nil {
87 | 			color.Red("Error writing config file: %s", err.Error())
88 | 			os.Exit(1)
89 | 		}
90 | 		color.Green("Filter %s added", strings.Join(inputFilters, ", "))
91 | 	},
92 | }
93 | 


--------------------------------------------------------------------------------
/cmd/filters/filters.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package filters
15 | 
16 | import (
17 | 	"github.com/spf13/cobra"
18 | )
19 | 
20 | var FiltersCmd = &cobra.Command{
21 | 	Use:     "filters",
22 | 	Aliases: []string{"filter"},
23 | 	Short:   "Manage filters for analyzing Kubernetes resources",
24 | 	Long: `The filters command allows you to manage filters that are used to analyze Kubernetes resources.
25 | 	You can list available filters to analyze resources.`,
26 | 	Run: func(cmd *cobra.Command, args []string) {
27 | 		if len(args) == 0 {
28 | 			_ = cmd.Help()
29 | 			return
30 | 		}
31 | 	},
32 | }
33 | 
34 | func init() {
35 | 	FiltersCmd.AddCommand(listCmd)
36 | 	FiltersCmd.AddCommand(addCmd)
37 | 	FiltersCmd.AddCommand(removeCmd)
38 | }
39 | 


--------------------------------------------------------------------------------
/cmd/filters/list.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package filters
15 | 
16 | import (
17 | 	"fmt"
18 | 	"slices"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
23 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
24 | 	"github.com/spf13/cobra"
25 | 	"github.com/spf13/viper"
26 | )
27 | 
28 | var listCmd = &cobra.Command{
29 | 	Use:   "list",
30 | 	Short: "List available filters",
31 | 	Long:  `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		activeFilters := viper.GetStringSlice("active_filters")
34 | 		coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
35 | 		integration := integration.NewIntegration()
36 | 		availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
37 | 
38 | 		if len(activeFilters) == 0 {
39 | 			activeFilters = coreFilters
40 | 		}
41 | 		inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
42 | 		fmt.Print(color.YellowString("Active: \n"))
43 | 		for _, filter := range activeFilters {
44 | 			// if the filter is an integration, mark this differently
45 | 			// but if the integration is inactive, remove
46 | 			if slices.Contains(integrationFilters, filter) {
47 | 				fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
48 | 			} else {
49 | 				// This strange bit of logic will loop through every integration via
50 | 				// OwnsAnalyzer subcommand to check the filter and as the integrationFilters...
51 | 				// was no match, we know this isn't part of an active integration
52 | 				if _, err := integration.AnalyzerByIntegration(filter); err != nil {
53 | 					fmt.Printf("> %s\n", color.GreenString(filter))
54 | 				}
55 | 			}
56 | 		}
57 | 
58 | 		// display inactive filters
59 | 		if len(inactiveFilters) != 0 {
60 | 			fmt.Print(color.YellowString("Unused: \n"))
61 | 			for _, filter := range inactiveFilters {
62 | 				// if the filter is an integration, mark this differently
63 | 				if slices.Contains(integrationFilters, filter) {
64 | 					fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
65 | 				} else {
66 | 					fmt.Printf("> %s\n", color.RedString(filter))
67 | 				}
68 | 			}
69 | 		}
70 | 	},
71 | }
72 | 


--------------------------------------------------------------------------------
/cmd/filters/remove.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package filters
15 | 
16 | import (
17 | 	"os"
18 | 	"strings"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
23 | 	"github.com/spf13/cobra"
24 | 	"github.com/spf13/viper"
25 | )
26 | 
27 | var removeCmd = &cobra.Command{
28 | 	Use:   "remove [filter(s)]",
29 | 	Short: "Remove one or more filters.",
30 | 	Long:  `The add command remove one or more filters to the default set of filters used by the analyze.`,
31 | 	Args:  cobra.ExactArgs(1),
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		inputFilters := strings.Split(args[0], ",")
34 | 
35 | 		// Get defined active_filters
36 | 		activeFilters := viper.GetStringSlice("active_filters")
37 | 		coreFilters, _, _ := analyzer.ListFilters()
38 | 
39 | 		if len(activeFilters) == 0 {
40 | 			activeFilters = coreFilters
41 | 		}
42 | 
43 | 		// Check if input input filters is not empty
44 | 		for _, f := range inputFilters {
45 | 			if f == "" {
46 | 				color.Red("Filter cannot be empty. Please use correct syntax.")
47 | 				os.Exit(1)
48 | 			}
49 | 		}
50 | 
51 | 		// verify dupplicate filters example: k8sgpt filters remove Pod Pod
52 | 		uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(inputFilters)
53 | 		if len(dupplicatedFilters) != 0 {
54 | 			color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
55 | 			os.Exit(1)
56 | 		}
57 | 
58 | 		// Verify if filter exist in config file and update default_filter
59 | 		filterNotFound := []string{}
60 | 		for _, filter := range uniqueFilters {
61 | 			foundFilter := false
62 | 			for i, f := range activeFilters {
63 | 				if f == filter {
64 | 					foundFilter = true
65 | 					activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
66 | 					break
67 | 				}
68 | 			}
69 | 			if !foundFilter {
70 | 				filterNotFound = append(filterNotFound, filter)
71 | 			}
72 | 		}
73 | 
74 | 		if len(filterNotFound) != 0 {
75 | 			color.Red("Filter(s) %s does not exist in configuration file. Please use k8sgpt filters add.", strings.Join(filterNotFound, ", "))
76 | 			os.Exit(1)
77 | 		}
78 | 
79 | 		viper.Set("active_filters", activeFilters)
80 | 
81 | 		if err := viper.WriteConfig(); err != nil {
82 | 			color.Red("Error writing config file: %s", err.Error())
83 | 			os.Exit(1)
84 | 		}
85 | 		color.Green("Filter(s) %s removed", strings.Join(inputFilters, ", "))
86 | 	},
87 | }
88 | 


--------------------------------------------------------------------------------
/cmd/generate/generate.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package generate
15 | 
16 | import (
17 | 	"fmt"
18 | 	"os/exec"
19 | 	"runtime"
20 | 
21 | 	"github.com/fatih/color"
22 | 	"github.com/spf13/cobra"
23 | 	"github.com/spf13/viper"
24 | )
25 | 
26 | var (
27 | 	backend     string
28 | 	backendType string
29 | )
30 | 
31 | // generateCmd represents the auth command
32 | var GenerateCmd = &cobra.Command{
33 | 	Use:   "generate",
34 | 	Short: "Generate Key for your chosen backend (opens browser)",
35 | 	Long:  `Opens your browser to generate a key for your chosen backend.`,
36 | 	Run: func(cmd *cobra.Command, args []string) {
37 | 
38 | 		backendType = viper.GetString("backend_type")
39 | 		if backendType == "" {
40 | 			// Set the default backend
41 | 			backend = "openai"
42 | 		}
43 | 		// override the default backend if a flag is provided
44 | 		if backend != "" {
45 | 			backendType = backend
46 | 		}
47 | 		fmt.Println("")
48 | 		openbrowser("https://platform.openai.com/api-keys")
49 | 	},
50 | }
51 | 
52 | func init() {
53 | 	// add flag for backend
54 | 	GenerateCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
55 | }
56 | 
57 | func openbrowser(url string) {
58 | 	var err error
59 | 	isGui := true
60 | 	switch runtime.GOOS {
61 | 	case "linux":
62 | 		_, err = exec.LookPath("xdg-open")
63 | 		if err != nil {
64 | 			isGui = false
65 | 		} else {
66 | 			err = exec.Command("xdg-open", url).Start()
67 | 		}
68 | 	case "windows":
69 | 		err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
70 | 	case "darwin":
71 | 		err = exec.Command("open", url).Start()
72 | 	default:
73 | 		err = fmt.Errorf("unsupported platform")
74 | 	}
75 | 	printInstructions(isGui, backend)
76 | 	if err != nil {
77 | 		fmt.Println(err)
78 | 	}
79 | }
80 | 
81 | func printInstructions(isGui bool, backendType string) {
82 | 	fmt.Println("")
83 | 	if isGui {
84 | 		color.Green("Opening: https://platform.openai.com/api-keys to generate a key for %s", backendType)
85 | 		fmt.Println("")
86 | 	} else {
87 | 		color.Green("Please open: https://platform.openai.com/api-keys to generate a key for %s", backendType)
88 | 		fmt.Println("")
89 | 	}
90 | 	color.Green("Please copy the generated key and run `k8sgpt auth add` to add it to your config file")
91 | 	fmt.Println("")
92 | }
93 | 


--------------------------------------------------------------------------------
/cmd/integration/activate.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package integration
15 | 
16 | import (
17 | 	"github.com/fatih/color"
18 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
20 | 	"github.com/spf13/cobra"
21 | 	"github.com/spf13/viper"
22 | )
23 | 
24 | var skipInstall bool
25 | 
26 | // activateCmd represents the activate command
27 | var activateCmd = &cobra.Command{
28 | 	Use:   "activate [integration]",
29 | 	Short: "Activate an integration",
30 | 	Long:  ``,
31 | 	Args:  cobra.ExactArgs(1),
32 | 	Run: func(cmd *cobra.Command, args []string) {
33 | 		integrationName := args[0]
34 | 		coreFilters, _, _ := analyzer.ListFilters()
35 | 
36 | 		// Update filters
37 | 		activeFilters := viper.GetStringSlice("active_filters")
38 | 		if len(activeFilters) == 0 {
39 | 			activeFilters = coreFilters
40 | 		}
41 | 
42 | 		integration := integration.NewIntegration()
43 | 		// Check if the integation exists
44 | 		err := integration.Activate(integrationName, namespace, activeFilters, skipInstall)
45 | 		if err != nil {
46 | 			color.Red("Error: %v", err)
47 | 			return
48 | 		}
49 | 
50 | 		color.Green("Activated integration %s", integrationName)
51 | 	},
52 | }
53 | 
54 | func init() {
55 | 	IntegrationCmd.AddCommand(activateCmd)
56 | 	activateCmd.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)")
57 | }
58 | 


--------------------------------------------------------------------------------
/cmd/integration/deactivate.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package integration
15 | 
16 | import (
17 | 	"github.com/fatih/color"
18 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
19 | 	"github.com/spf13/cobra"
20 | )
21 | 
22 | // deactivateCmd represents the deactivate command
23 | var deactivateCmd = &cobra.Command{
24 | 	Use:   "deactivate [integration]",
25 | 	Short: "Deactivate an integration",
26 | 	Args:  cobra.ExactArgs(1),
27 | 	Long:  `For example e.g. k8sgpt integration deactivate prometheus`,
28 | 	Run: func(cmd *cobra.Command, args []string) {
29 | 		integrationName := args[0]
30 | 
31 | 		integration := integration.NewIntegration()
32 | 
33 | 		if err := integration.Deactivate(integrationName, namespace); err != nil {
34 | 			color.Red("Error: %v", err)
35 | 			return
36 | 		}
37 | 
38 | 		color.Green("Deactivated integration %s", integrationName)
39 | 
40 | 	},
41 | }
42 | 
43 | func init() {
44 | 	IntegrationCmd.AddCommand(deactivateCmd)
45 | }
46 | 


--------------------------------------------------------------------------------
/cmd/integration/integration.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package integration
15 | 
16 | import (
17 | 	"github.com/spf13/cobra"
18 | )
19 | 
20 | var (
21 | 	namespace string
22 | )
23 | 
24 | // IntegrationCmd represents the integrate command
25 | var IntegrationCmd = &cobra.Command{
26 | 	Use:     "integration",
27 | 	Aliases: []string{"integrations"},
28 | 	Short:   "Integrate another tool into K8sGPT",
29 | 	Long: `Integrate another tool into K8sGPT. For example:
30 | 	
31 | 	k8sgpt integration activate prometheus
32 | 	
33 | 	This would allow you to connect to prometheus running with your cluster.`,
34 | 	Run: func(cmd *cobra.Command, args []string) {
35 | 		_ = cmd.Help()
36 | 	},
37 | }
38 | 
39 | func init() {
40 | 	IntegrationCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to use for the integration")
41 | }
42 | 


--------------------------------------------------------------------------------
/cmd/integration/list.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package integration
15 | 
16 | import (
17 | 	"fmt"
18 | 	"os"
19 | 
20 | 	"github.com/fatih/color"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
22 | 	"github.com/spf13/cobra"
23 | )
24 | 
25 | // listCmd represents the list command
26 | var listCmd = &cobra.Command{
27 | 	Use:   "list",
28 | 	Short: "Lists built-in integrations",
29 | 	Long:  ``,
30 | 	Run: func(cmd *cobra.Command, args []string) {
31 | 		integrationProvider := integration.NewIntegration()
32 | 		integrations := integrationProvider.List()
33 | 
34 | 		fmt.Println(color.YellowString("Active:"))
35 | 		for _, i := range integrations {
36 | 			b, err := integrationProvider.IsActivate(i)
37 | 			if err != nil {
38 | 				fmt.Println(err)
39 | 				os.Exit(1)
40 | 			}
41 | 			if b {
42 | 				fmt.Printf("> %s\n", color.GreenString(i))
43 | 			}
44 | 		}
45 | 
46 | 		fmt.Println(color.YellowString("Unused: "))
47 | 		for _, i := range integrations {
48 | 			b, err := integrationProvider.IsActivate(i)
49 | 			if err != nil {
50 | 				fmt.Println(err)
51 | 				os.Exit(1)
52 | 			}
53 | 			if !b {
54 | 				fmt.Printf("> %s\n", color.GreenString(i))
55 | 			}
56 | 		}
57 | 	},
58 | }
59 | 
60 | func init() {
61 | 	IntegrationCmd.AddCommand(listCmd)
62 | 
63 | }
64 | 


--------------------------------------------------------------------------------
/cmd/root_test.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package cmd
15 | 
16 | import (
17 | 	"testing"
18 | 
19 | 	"github.com/spf13/viper"
20 | )
21 | 
22 | // Test that verbose flag is correctly set in viper.
23 | func TestInitConfig_VerboseFlag(t *testing.T) {
24 | 	verbose = true
25 | 	viper.Reset()
26 | 	initConfig()
27 | 	if !viper.GetBool("verbose") {
28 | 		t.Error("Expected verbose flag to be true")
29 | 	}
30 | }
31 | 


--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package cmd
15 | 
16 | import (
17 | 	"fmt"
18 | 	"runtime/debug"
19 | 
20 | 	"github.com/spf13/cobra"
21 | )
22 | 
23 | // versionCmd represents the version command
24 | var versionCmd = &cobra.Command{
25 | 	Use:   "version",
26 | 	Short: "Print the version number of k8sgpt",
27 | 	Long:  `All software has versions. This is k8sgpt's`,
28 | 	Run: func(cmd *cobra.Command, args []string) {
29 | 		if Version == "dev" {
30 | 			details, ok := debug.ReadBuildInfo()
31 | 			if ok && details.Main.Version != "" && details.Main.Version != "(devel)" {
32 | 				Version = details.Main.Version
33 | 				for _, i := range details.Settings {
34 | 					if i.Key == "vcs.time" {
35 | 						Date = i.Value
36 | 					}
37 | 					if i.Key == "vcs.revision" {
38 | 						Commit = i.Value
39 | 					}
40 | 				}
41 | 			}
42 | 		}
43 | 		fmt.Printf("k8sgpt: %s (%s), built at: %s\n", Version, Commit, Date)
44 | 	},
45 | }
46 | 
47 | func init() {
48 | 	rootCmd.AddCommand(versionCmd)
49 | }
50 | 


--------------------------------------------------------------------------------
/container/Dockerfile:
--------------------------------------------------------------------------------
 1 | # Copyright 2023 The K8sGPT Authors.
 2 | # Licensed under the Apache License, Version 2.0 (the "License");
 3 | # you may not use this file except in compliance with the License.
 4 | # You may obtain a copy of the License at
 5 | #     http://www.apache.org/licenses/LICENSE-2.0
 6 | # Unless required by applicable law or agreed to in writing, software
 7 | # distributed under the License is distributed on an "AS IS" BASIS,
 8 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 9 | # See the License for the specific language governing permissions and
10 | # limitations under the License.
11 | 
12 | FROM golang:1.23-alpine3.19 AS builder
13 | 
14 | ENV CGO_ENABLED=0
15 | ARG VERSION
16 | ARG COMMIT
17 | ARG DATE
18 | WORKDIR /workspace
19 | 
20 | COPY go.mod go.sum ./
21 | RUN go mod download
22 | 
23 | COPY ./ ./
24 | 
25 | RUN go build -o /workspace/k8sgpt -ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" ./ 
26 | 
27 | FROM gcr.io/distroless/static AS production
28 | 
29 | LABEL org.opencontainers.image.source="https://github.com/k8sgpt-ai/k8sgpt" \
30 |     org.opencontainers.image.url="https://k8sgpt.ai" \
31 |     org.opencontainers.image.title="k8sgpt" \
32 |     org.opencontainers.image.vendor='The K8sGPT Authors' \
33 |     org.opencontainers.image.licenses='Apache-2.0'
34 | 
35 | WORKDIR /
36 | COPY --from=builder /workspace/k8sgpt .
37 | USER 65532:65532
38 | 
39 | ENTRYPOINT ["/k8sgpt"]
40 | 


--------------------------------------------------------------------------------
/images/banner-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/banner-black.png


--------------------------------------------------------------------------------
/images/banner-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/banner-white.png


--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo.gif


--------------------------------------------------------------------------------
/images/demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo1.gif


--------------------------------------------------------------------------------
/images/demo2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo2.gif


--------------------------------------------------------------------------------
/images/demo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo3.png


--------------------------------------------------------------------------------
/images/demo4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo4.gif


--------------------------------------------------------------------------------
/images/demo5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/demo5.gif


--------------------------------------------------------------------------------
/images/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/image.png


--------------------------------------------------------------------------------
/images/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/landing.png


--------------------------------------------------------------------------------
/images/nodes.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/k8sgpt-ai/k8sgpt/1819e6f410d078fce2bda8bbdb22054dfb4fc092/images/nodes.gif


--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package main
15 | 
16 | import "github.com/k8sgpt-ai/k8sgpt/cmd"
17 | 
18 | var (
19 | 	version = "dev"
20 | 	commit  = "HEAD"
21 | 	date    = "unknown"
22 | )
23 | 
24 | func main() {
25 | 	cmd.Execute(version, commit, date)
26 | }
27 | 


--------------------------------------------------------------------------------
/pkg/ai/amazonbedrock_mock_test.go:
--------------------------------------------------------------------------------
  1 | package ai
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"testing"
  6 | 
  7 | 	"github.com/aws/aws-sdk-go-v2/aws"
  8 | 	"github.com/aws/aws-sdk-go-v2/service/bedrock"
  9 | 	"github.com/aws/aws-sdk-go-v2/service/bedrock/types"
 10 | 	"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
 11 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support"
 12 | 	"github.com/stretchr/testify/assert"
 13 | 	"github.com/stretchr/testify/mock"
 14 | )
 15 | 
 16 | // Mock for Bedrock Management Client
 17 | type MockBedrockClient struct {
 18 | 	mock.Mock
 19 | }
 20 | 
 21 | func (m *MockBedrockClient) GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error) {
 22 | 	args := m.Called(ctx, params)
 23 | 	
 24 | 	if args.Get(0) == nil {
 25 | 		return nil, args.Error(1)
 26 | 	}
 27 | 	
 28 | 	return args.Get(0).(*bedrock.GetInferenceProfileOutput), args.Error(1)
 29 | }
 30 | 
 31 | // Mock for Bedrock Runtime Client
 32 | type MockBedrockRuntimeClient struct {
 33 | 	mock.Mock
 34 | }
 35 | 
 36 | func (m *MockBedrockRuntimeClient) InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error) {
 37 | 	args := m.Called(ctx, params)
 38 | 	
 39 | 	if args.Get(0) == nil {
 40 | 		return nil, args.Error(1)
 41 | 	}
 42 | 	
 43 | 	return args.Get(0).(*bedrockruntime.InvokeModelOutput), args.Error(1)
 44 | }
 45 | 
 46 | // TestBedrockInferenceProfileARNWithMocks tests the inference profile ARN validation with mocks
 47 | func TestBedrockInferenceProfileARNWithMocks(t *testing.T) {
 48 | 	// Create test models
 49 | 	testModels := []bedrock_support.BedrockModel{
 50 | 		{
 51 | 			Name:       "anthropic.claude-3-5-sonnet-20240620-v1:0",
 52 | 			Completion: &bedrock_support.CohereMessagesCompletion{},
 53 | 			Response:   &bedrock_support.CohereMessagesResponse{},
 54 | 			Config: bedrock_support.BedrockModelConfig{
 55 | 				MaxTokens:   100,
 56 | 				Temperature: 0.5,
 57 | 				TopP:        0.9,
 58 | 				ModelName:   "anthropic.claude-3-5-sonnet-20240620-v1:0",
 59 | 			},
 60 | 		},
 61 | 	}
 62 | 	
 63 | 	// Create a client with test models
 64 | 	client := &AmazonBedRockClient{models: testModels}
 65 | 	
 66 | 	// Create mock clients
 67 | 	mockMgmtClient := new(MockBedrockClient)
 68 | 	mockRuntimeClient := new(MockBedrockRuntimeClient)
 69 | 	
 70 | 	// Inject mock clients into the AmazonBedRockClient
 71 | 	client.mgmtClient = mockMgmtClient
 72 | 	client.client = mockRuntimeClient
 73 | 	
 74 | 	// Test with a valid inference profile ARN
 75 | 	inferenceProfileARN := "arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile"
 76 | 	
 77 | 	// Setup mock response for GetInferenceProfile
 78 | 	mockMgmtClient.On("GetInferenceProfile", mock.Anything, &bedrock.GetInferenceProfileInput{
 79 | 		InferenceProfileIdentifier: aws.String("my-profile"),
 80 | 	}).Return(&bedrock.GetInferenceProfileOutput{
 81 | 		Models: []types.InferenceProfileModel{
 82 | 			{
 83 | 				ModelArn: aws.String("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"),
 84 | 			},
 85 | 		},
 86 | 	}, nil)
 87 | 	
 88 | 	// Configure the client with the inference profile ARN
 89 | 	config := AIProvider{
 90 | 		Model:          inferenceProfileARN,
 91 | 		ProviderRegion: "us-east-1",
 92 | 	}
 93 | 	
 94 | 	// Test the Configure method with the inference profile ARN
 95 | 	err := client.Configure(&config)
 96 | 	
 97 | 	// Verify that the configuration was successful
 98 | 	assert.NoError(t, err)
 99 | 	assert.Equal(t, inferenceProfileARN, client.model.Config.ModelName)
100 | 	
101 | 	// Verify that the mock was called
102 | 	mockMgmtClient.AssertExpectations(t)
103 | }
104 | 


--------------------------------------------------------------------------------
/pkg/ai/azureopenai.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"errors"
 6 | 	"net/http"
 7 | 	"net/url"
 8 | 
 9 | 	"github.com/sashabaranov/go-openai"
10 | )
11 | 
12 | const azureAIClientName = "azureopenai"
13 | 
14 | type AzureAIClient struct {
15 | 	nopCloser
16 | 
17 | 	client      *openai.Client
18 | 	model       string
19 | 	temperature float32
20 | 	// organizationId string
21 | }
22 | 
23 | func (c *AzureAIClient) Configure(config IAIConfig) error {
24 | 	token := config.GetPassword()
25 | 	baseURL := config.GetBaseURL()
26 | 	engine := config.GetEngine()
27 | 	proxyEndpoint := config.GetProxyEndpoint()
28 | 	defaultConfig := openai.DefaultAzureConfig(token, baseURL)
29 | 	orgId := config.GetOrganizationId()
30 | 
31 | 	defaultConfig.AzureModelMapperFunc = func(model string) string {
32 | 		// If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function
33 | 		azureModelMapping := map[string]string{
34 | 			model: engine,
35 | 		}
36 | 		return azureModelMapping[model]
37 | 
38 | 	}
39 | 
40 | 	if proxyEndpoint != "" {
41 | 		proxyUrl, err := url.Parse(proxyEndpoint)
42 | 		if err != nil {
43 | 			return err
44 | 		}
45 | 		transport := &http.Transport{
46 | 			Proxy: http.ProxyURL(proxyUrl),
47 | 		}
48 | 
49 | 		defaultConfig.HTTPClient = &http.Client{
50 | 			Transport: transport,
51 | 		}
52 | 	}
53 | 	if orgId != "" {
54 | 		defaultConfig.OrgID = orgId
55 | 	}
56 | 
57 | 	client := openai.NewClientWithConfig(defaultConfig)
58 | 	if client == nil {
59 | 		return errors.New("error creating Azure OpenAI client")
60 | 	}
61 | 	c.client = client
62 | 	c.model = config.GetModel()
63 | 	c.temperature = config.GetTemperature()
64 | 	return nil
65 | }
66 | 
67 | func (c *AzureAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
68 | 	// Create a completion request
69 | 	resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
70 | 		Model: c.model,
71 | 		Messages: []openai.ChatCompletionMessage{
72 | 			{
73 | 				Role:    openai.ChatMessageRoleUser,
74 | 				Content: prompt,
75 | 			},
76 | 		},
77 | 		Temperature: c.temperature,
78 | 	})
79 | 	if err != nil {
80 | 		return "", err
81 | 	}
82 | 	return resp.Choices[0].Message.Content, nil
83 | }
84 | 
85 | func (c *AzureAIClient) GetName() string {
86 | 	return azureAIClientName
87 | }
88 | 


--------------------------------------------------------------------------------
/pkg/ai/bedrock_interfaces.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/aws/aws-sdk-go-v2/service/bedrock"
 7 | 	"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
 8 | )
 9 | 
10 | // BedrockManagementAPI defines the interface for Bedrock management operations
11 | type BedrockManagementAPI interface {
12 | 	GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error)
13 | }
14 | 
15 | // BedrockRuntimeAPI defines the interface for Bedrock runtime operations
16 | type BedrockRuntimeAPI interface {
17 | 	InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error)
18 | }
19 | 


--------------------------------------------------------------------------------
/pkg/ai/bedrock_support/model.go:
--------------------------------------------------------------------------------
 1 | package bedrock_support
 2 | 
 3 | type BedrockModelConfig struct {
 4 | 	MaxTokens   int
 5 | 	Temperature float32
 6 | 	TopP        float32
 7 | 	ModelName   string
 8 | }
 9 | type BedrockModel struct {
10 | 	Name       string
11 | 	Completion ICompletion
12 | 	Response   IResponse
13 | 	Config     BedrockModelConfig
14 | }
15 | 


--------------------------------------------------------------------------------
/pkg/ai/bedrock_support/model_test.go:
--------------------------------------------------------------------------------
 1 | package bedrock_support
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"testing"
 6 | 
 7 | 	"github.com/stretchr/testify/assert"
 8 | )
 9 | 
10 | func TestBedrockModelConfig(t *testing.T) {
11 | 	config := BedrockModelConfig{
12 | 		MaxTokens:   100,
13 | 		Temperature: 0.7,
14 | 		TopP:        0.9,
15 | 		ModelName:   "test-model",
16 | 	}
17 | 
18 | 	assert.Equal(t, 100, config.MaxTokens)
19 | 	assert.Equal(t, float32(0.7), config.Temperature)
20 | 	assert.Equal(t, float32(0.9), config.TopP)
21 | 	assert.Equal(t, "test-model", config.ModelName)
22 | }
23 | 
24 | func TestBedrockModel(t *testing.T) {
25 | 	completion := &MockCompletion{}
26 | 	response := &MockResponse{}
27 | 	config := BedrockModelConfig{
28 | 		MaxTokens:   100,
29 | 		Temperature: 0.7,
30 | 		TopP:        0.9,
31 | 		ModelName:   "test-model",
32 | 	}
33 | 
34 | 	model := BedrockModel{
35 | 		Name:       "Test Model",
36 | 		Completion: completion,
37 | 		Response:   response,
38 | 		Config:     config,
39 | 	}
40 | 
41 | 	assert.Equal(t, "Test Model", model.Name)
42 | 	assert.Equal(t, completion, model.Completion)
43 | 	assert.Equal(t, response, model.Response)
44 | 	assert.Equal(t, config, model.Config)
45 | }
46 | 
47 | // MockCompletion is a mock implementation of the ICompletion interface
48 | type MockCompletion struct{}
49 | 
50 | func (m *MockCompletion) GetCompletion(ctx context.Context, prompt string, config BedrockModelConfig) ([]byte, error) {
51 | 	return []byte(`{"prompt": "mock prompt"}`), nil
52 | }
53 | 
54 | // MockResponse is a mock implementation of the IResponse interface
55 | type MockResponse struct{}
56 | 
57 | func (m *MockResponse) ParseResponse(body []byte) (string, error) {
58 | 	return "mock response", nil
59 | }
60 | 


--------------------------------------------------------------------------------
/pkg/ai/bedrock_support/responses_test.go:
--------------------------------------------------------------------------------
 1 | package bedrock_support
 2 | 
 3 | import (
 4 | 	"testing"
 5 | 
 6 | 	"github.com/stretchr/testify/assert"
 7 | )
 8 | 
 9 | func TestCohereResponse_ParseResponse(t *testing.T) {
10 | 	response := &CohereResponse{}
11 | 	rawResponse := []byte(`{"completion": "Test completion", "stop_reason": "max_tokens"}`)
12 | 
13 | 	result, err := response.ParseResponse(rawResponse)
14 | 	assert.NoError(t, err)
15 | 	assert.Equal(t, "Test completion", result)
16 | 
17 | 	invalidResponse := []byte(`{"completion": "Test completion", "invalid_json":]`)
18 | 	_, err = response.ParseResponse(invalidResponse)
19 | 	assert.Error(t, err)
20 | }
21 | 
22 | func TestAI21Response_ParseResponse(t *testing.T) {
23 | 	response := &AI21Response{}
24 | 	rawResponse := []byte(`{"completions": [{"data": {"text": "AI21 test"}}], "id": "123"}`)
25 | 
26 | 	result, err := response.ParseResponse(rawResponse)
27 | 	assert.NoError(t, err)
28 | 	assert.Equal(t, "AI21 test", result)
29 | 
30 | 	invalidResponse := []byte(`{"completions": [{"data": {"text": "AI21 test"}}, "invalid_json":]`)
31 | 	_, err = response.ParseResponse(invalidResponse)
32 | 	assert.Error(t, err)
33 | }
34 | 
35 | func TestAmazonResponse_ParseResponse(t *testing.T) {
36 | 	response := &AmazonResponse{}
37 | 	rawResponse := []byte(`{"inputTextTokenCount": 10, "results": [{"tokenCount": 20, "outputText": "Amazon test", "completionReason": "stop"}]}`)
38 | 
39 | 	result, err := response.ParseResponse(rawResponse)
40 | 	assert.NoError(t, err)
41 | 	assert.Equal(t, "Amazon test", result)
42 | 
43 | 	invalidResponse := []byte(`{"inputTextTokenCount": 10, "results": [{"tokenCount": 20, "outputText": "Amazon test", "invalid_json":]`)
44 | 	_, err = response.ParseResponse(invalidResponse)
45 | 	assert.Error(t, err)
46 | }
47 | 
48 | func TestNovaResponse_ParseResponse(t *testing.T) {
49 | 	response := &NovaResponse{}
50 | 	rawResponse := []byte(`{"output": {"message": {"content": [{"text": "Nova test"}]}}, "stopReason": "stop", "usage": {"inputTokens": 10, "outputTokens": 20, "totalTokens": 30, "cacheReadInputTokenCount": 5}}`)
51 | 
52 | 	result, err := response.ParseResponse(rawResponse)
53 | 	assert.NoError(t, err)
54 | 	assert.Equal(t, "Nova test", result)
55 | 
56 | 	rawResponseEmptyContent := []byte(`{"output": {"message": {"content": []}}, "stopReason": "stop", "usage": {"inputTokens": 10, "outputTokens": 20, "totalTokens": 30, "cacheReadInputTokenCount": 5}}`)
57 | 
58 | 	resultEmptyContent, errEmptyContent := response.ParseResponse(rawResponseEmptyContent)
59 | 	assert.NoError(t, errEmptyContent)
60 | 	assert.Equal(t, "", resultEmptyContent)
61 | 
62 | 	invalidResponse := []byte(`{"output": {"message": {"content": [{"text": "Nova test"}}, "invalid_json":]`)
63 | 	_, err = response.ParseResponse(invalidResponse)
64 | 	assert.Error(t, err)
65 | }
66 | 


--------------------------------------------------------------------------------
/pkg/ai/cohere.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package ai
15 | 
16 | import (
17 | 	"context"
18 | 	"errors"
19 | 
20 | 	api "github.com/cohere-ai/cohere-go/v2"
21 | 	cohere "github.com/cohere-ai/cohere-go/v2/client"
22 | 	"github.com/cohere-ai/cohere-go/v2/option"
23 | )
24 | 
25 | const cohereAIClientName = "cohere"
26 | 
27 | type CohereClient struct {
28 | 	nopCloser
29 | 
30 | 	client      *cohere.Client
31 | 	model       string
32 | 	temperature float32
33 | 	maxTokens   int
34 | }
35 | 
36 | func (c *CohereClient) Configure(config IAIConfig) error {
37 | 	token := config.GetPassword()
38 | 
39 | 	opts := []option.RequestOption{
40 | 		cohere.WithToken(token),
41 | 	}
42 | 
43 | 	baseURL := config.GetBaseURL()
44 | 	if baseURL != "" {
45 | 		opts = append(opts, cohere.WithBaseURL(baseURL))
46 | 	}
47 | 
48 | 	client := cohere.NewClient(opts...)
49 | 	if client == nil {
50 | 		return errors.New("error creating Cohere client")
51 | 	}
52 | 
53 | 	c.client = client
54 | 	c.model = config.GetModel()
55 | 	c.temperature = config.GetTemperature()
56 | 	c.maxTokens = config.GetMaxTokens()
57 | 
58 | 	return nil
59 | }
60 | 
61 | func (c *CohereClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
62 | 	// Create a completion request
63 | 	response, err := c.client.Chat(ctx, &api.ChatRequest{
64 | 		Message:      prompt,
65 | 		Model:        &c.model,
66 | 		K:            api.Int(0),
67 | 		Preamble:     api.String(""),
68 | 		Temperature:  api.Float64(float64(c.temperature)),
69 | 		RawPrompting: api.Bool(false),
70 | 		MaxTokens:    api.Int(c.maxTokens),
71 | 	})
72 | 	if err != nil {
73 | 		return "", err
74 | 	}
75 | 	return response.Text, nil
76 | }
77 | 
78 | func (c *CohereClient) GetName() string {
79 | 	return cohereAIClientName
80 | }
81 | 


--------------------------------------------------------------------------------
/pkg/ai/factory.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package ai
15 | 
16 | import (
17 | 	"github.com/spf13/viper"
18 | )
19 | 
20 | // AIClientFactory is an interface for creating AI clients
21 | type AIClientFactory interface {
22 | 	NewClient(provider string) IAI
23 | }
24 | 
25 | // DefaultAIClientFactory is the default implementation of AIClientFactory
26 | type DefaultAIClientFactory struct{}
27 | 
28 | // NewClient creates a new AI client using the default implementation
29 | func (f *DefaultAIClientFactory) NewClient(provider string) IAI {
30 | 	return NewClient(provider)
31 | }
32 | 
33 | // ConfigProvider is an interface for accessing configuration
34 | type ConfigProvider interface {
35 | 	UnmarshalKey(key string, rawVal interface{}) error
36 | }
37 | 
38 | // ViperConfigProvider is the default implementation of ConfigProvider using Viper
39 | type ViperConfigProvider struct{}
40 | 
41 | // UnmarshalKey unmarshals a key from the configuration using Viper
42 | func (p *ViperConfigProvider) UnmarshalKey(key string, rawVal interface{}) error {
43 | 	return viper.UnmarshalKey(key, rawVal)
44 | }
45 | 
46 | // Default instances to be used
47 | var (
48 | 	DefaultClientFactory = &DefaultAIClientFactory{}
49 | 	DefaultConfigProvider = &ViperConfigProvider{}
50 | )
51 | 
52 | // For testing - these variables can be overridden in tests
53 | var (
54 | 	testAIClientFactory AIClientFactory = nil
55 | 	testConfigProvider  ConfigProvider  = nil
56 | )
57 | 
58 | // GetAIClientFactory returns the test factory if set, otherwise the default
59 | func GetAIClientFactory() AIClientFactory {
60 | 	if testAIClientFactory != nil {
61 | 		return testAIClientFactory
62 | 	}
63 | 	return DefaultClientFactory
64 | }
65 | 
66 | // GetConfigProvider returns the test provider if set, otherwise the default
67 | func GetConfigProvider() ConfigProvider {
68 | 	if testConfigProvider != nil {
69 | 		return testConfigProvider
70 | 	}
71 | 	return DefaultConfigProvider
72 | }
73 | 
74 | // For testing - set the test implementations
75 | func SetTestAIClientFactory(factory AIClientFactory) {
76 | 	testAIClientFactory = factory
77 | }
78 | 
79 | func SetTestConfigProvider(provider ConfigProvider) {
80 | 	testConfigProvider = provider
81 | }
82 | 
83 | // Reset test implementations
84 | func ResetTestImplementations() {
85 | 	testAIClientFactory = nil
86 | 	testConfigProvider = nil
87 | }


--------------------------------------------------------------------------------
/pkg/ai/huggingface.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | import (
 4 | 	"context"
 5 | 
 6 | 	"github.com/hupe1980/go-huggingface"
 7 | 	"k8s.io/utils/ptr"
 8 | )
 9 | 
10 | const huggingfaceAIClientName = "huggingface"
11 | 
12 | type HuggingfaceClient struct {
13 | 	nopCloser
14 | 
15 | 	client      *huggingface.InferenceClient
16 | 	model       string
17 | 	topP        float32
18 | 	topK        int32
19 | 	temperature float32
20 | 	maxTokens   int
21 | }
22 | 
23 | func (c *HuggingfaceClient) Configure(config IAIConfig) error {
24 | 	token := config.GetPassword()
25 | 
26 | 	client := huggingface.NewInferenceClient(token)
27 | 
28 | 	c.client = client
29 | 	c.model = config.GetModel()
30 | 	c.topP = config.GetTopP()
31 | 	c.topK = config.GetTopK()
32 | 	c.temperature = config.GetTemperature()
33 | 	if config.GetMaxTokens() > 500 {
34 | 		c.maxTokens = 500
35 | 	} else {
36 | 		c.maxTokens = config.GetMaxTokens()
37 | 	}
38 | 	return nil
39 | }
40 | 
41 | func (c *HuggingfaceClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
42 | 	resp, err := c.client.Conversational(ctx, &huggingface.ConversationalRequest{
43 | 		Inputs: huggingface.ConverstationalInputs{
44 | 			Text: prompt,
45 | 		},
46 | 		Model: c.model,
47 | 		Parameters: huggingface.ConversationalParameters{
48 | 			TopP:        ptr.To[float64](float64(c.topP)),
49 | 			TopK:        ptr.To[int](int(c.topK)),
50 | 			Temperature: ptr.To[float64](float64(c.temperature)),
51 | 			MaxLength:   &c.maxTokens,
52 | 		},
53 | 		Options: huggingface.Options{
54 | 			WaitForModel: ptr.To[bool](true),
55 | 		},
56 | 	})
57 | 	if err != nil {
58 | 		return "", err
59 | 	}
60 | 	return resp.GeneratedText, nil
61 | }
62 | 
63 | func (c *HuggingfaceClient) GetName() string { return huggingfaceAIClientName }
64 | 


--------------------------------------------------------------------------------
/pkg/ai/interactive/interactive.go:
--------------------------------------------------------------------------------
 1 | package interactive
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"strings"
 6 | 
 7 | 	"github.com/fatih/color"
 8 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
 9 | 	"github.com/pterm/pterm"
10 | )
11 | 
12 | type INTERACTIVE_STATE int
13 | 
14 | const (
15 | 	prompt = "Given the following context: "
16 | )
17 | 
18 | const (
19 | 	E_RUNNING INTERACTIVE_STATE = iota
20 | 	E_EXITED                    = iota
21 | )
22 | 
23 | type InteractionRunner struct {
24 | 	config        *analysis.Analysis
25 | 	State         chan INTERACTIVE_STATE
26 | 	contextWindow []byte
27 | }
28 | 
29 | func NewInteractionRunner(config *analysis.Analysis, contextWindow []byte) *InteractionRunner {
30 | 	return &InteractionRunner{
31 | 		config:        config,
32 | 		contextWindow: contextWindow,
33 | 		State:         make(chan INTERACTIVE_STATE),
34 | 	}
35 | }
36 | 
37 | func (a *InteractionRunner) StartInteraction() {
38 | 	a.State <- E_RUNNING
39 | 	pterm.Println("Interactive mode enabled [type exit to close.]")
40 | 	for {
41 | 
42 | 		query := pterm.DefaultInteractiveTextInput.WithMultiLine(false)
43 | 		queryString, err := query.Show()
44 | 		if err != nil {
45 | 			fmt.Println(err)
46 | 		}
47 | 		if queryString == "" {
48 | 			continue
49 | 		}
50 | 		if strings.Contains(queryString, "exit") {
51 | 			a.State <- E_EXITED
52 | 			continue
53 | 		}
54 | 		pterm.Println()
55 | 		contextWindow := fmt.Sprintf("%s %s %s", prompt, string(a.contextWindow),
56 | 			queryString)
57 | 
58 | 		response, err := a.config.AIClient.GetCompletion(a.config.Context,
59 | 			contextWindow)
60 | 		if err != nil {
61 | 			color.Red("Error: %v", err)
62 | 			a.State <- E_EXITED
63 | 			continue
64 | 		}
65 | 		pterm.Println(response)
66 | 	}
67 | }
68 | 


--------------------------------------------------------------------------------
/pkg/ai/localai.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | const localAIClientName = "localai"
 4 | 
 5 | type LocalAIClient struct {
 6 | 	OpenAIClient
 7 | }
 8 | 
 9 | func (a *LocalAIClient) GetName() string {
10 | 	return localAIClientName
11 | }
12 | 


--------------------------------------------------------------------------------
/pkg/ai/noopai.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package ai
15 | 
16 | import (
17 | 	"context"
18 | )
19 | 
20 | const noopAIClientName = "noopai"
21 | 
22 | type NoOpAIClient struct {
23 | 	nopCloser
24 | }
25 | 
26 | func (c *NoOpAIClient) Configure(_ IAIConfig) error {
27 | 	return nil
28 | }
29 | 
30 | func (c *NoOpAIClient) GetCompletion(_ context.Context, prompt string) (string, error) {
31 | 	response := "I am a noop response to the prompt " + prompt
32 | 	return response, nil
33 | }
34 | 
35 | func (c *NoOpAIClient) GetName() string {
36 | 	return noopAIClientName
37 | }
38 | 


--------------------------------------------------------------------------------
/pkg/ai/ocigenai.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2024 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package ai
15 | 
16 | import (
17 | 	"context"
18 | 	"errors"
19 | 	"github.com/oracle/oci-go-sdk/v65/common"
20 | 	"github.com/oracle/oci-go-sdk/v65/generativeaiinference"
21 | 	"strings"
22 | )
23 | 
24 | const ociClientName = "oci"
25 | 
26 | type OCIGenAIClient struct {
27 | 	nopCloser
28 | 
29 | 	client        *generativeaiinference.GenerativeAiInferenceClient
30 | 	model         string
31 | 	compartmentId string
32 | 	temperature   float32
33 | 	topP          float32
34 | 	maxTokens     int
35 | }
36 | 
37 | func (c *OCIGenAIClient) GetName() string {
38 | 	return ociClientName
39 | }
40 | 
41 | func (c *OCIGenAIClient) Configure(config IAIConfig) error {
42 | 	config.GetEndpointName()
43 | 	c.model = config.GetModel()
44 | 	c.temperature = config.GetTemperature()
45 | 	c.topP = config.GetTopP()
46 | 	c.maxTokens = config.GetMaxTokens()
47 | 	c.compartmentId = config.GetCompartmentId()
48 | 	provider := common.DefaultConfigProvider()
49 | 	client, err := generativeaiinference.NewGenerativeAiInferenceClientWithConfigurationProvider(provider)
50 | 	if err != nil {
51 | 		return err
52 | 	}
53 | 	c.client = &client
54 | 	return nil
55 | }
56 | 
57 | func (c *OCIGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
58 | 	generateTextRequest := c.newGenerateTextRequest(prompt)
59 | 	generateTextResponse, err := c.client.GenerateText(ctx, generateTextRequest)
60 | 	if err != nil {
61 | 		return "", err
62 | 	}
63 | 	return extractGeneratedText(generateTextResponse.InferenceResponse)
64 | }
65 | 
66 | func (c *OCIGenAIClient) newGenerateTextRequest(prompt string) generativeaiinference.GenerateTextRequest {
67 | 	temperatureF64 := float64(c.temperature)
68 | 	topPF64 := float64(c.topP)
69 | 	return generativeaiinference.GenerateTextRequest{
70 | 		GenerateTextDetails: generativeaiinference.GenerateTextDetails{
71 | 			CompartmentId: &c.compartmentId,
72 | 			ServingMode: generativeaiinference.OnDemandServingMode{
73 | 				ModelId: &c.model,
74 | 			},
75 | 			InferenceRequest: generativeaiinference.CohereLlmInferenceRequest{
76 | 				Prompt:      &prompt,
77 | 				MaxTokens:   &c.maxTokens,
78 | 				Temperature: &temperatureF64,
79 | 				TopP:        &topPF64,
80 | 			},
81 | 		},
82 | 	}
83 | }
84 | 
85 | func extractGeneratedText(llmInferenceResponse generativeaiinference.LlmInferenceResponse) (string, error) {
86 | 	response, ok := llmInferenceResponse.(generativeaiinference.CohereLlmInferenceResponse)
87 | 	if !ok {
88 | 		return "", errors.New("failed to extract generated text from backed response")
89 | 	}
90 | 	sb := strings.Builder{}
91 | 	for _, text := range response.GeneratedTexts {
92 | 		if text.Text != nil {
93 | 			sb.WriteString(*text.Text)
94 | 		}
95 | 	}
96 | 	return sb.String(), nil
97 | }
98 | 


--------------------------------------------------------------------------------
/pkg/ai/ollama.go:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright 2023 The K8sGPT Authors.
  3 | Licensed under the Apache License, Version 2.0 (the "License");
  4 | you may not use this file except in compliance with the License.
  5 | You may obtain a copy of the License at
  6 |     http://www.apache.org/licenses/LICENSE-2.0
  7 | Unless required by applicable law or agreed to in writing, software
  8 | distributed under the License is distributed on an "AS IS" BASIS,
  9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 10 | See the License for the specific language governing permissions and
 11 | limitations under the License.
 12 | */
 13 | 
 14 | package ai
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"errors"
 19 | 	"net/http"
 20 | 	"net/url"
 21 | 
 22 | 	ollama "github.com/ollama/ollama/api"
 23 | )
 24 | 
 25 | const ollamaClientName = "ollama"
 26 | 
 27 | type OllamaClient struct {
 28 | 	nopCloser
 29 | 
 30 | 	client      *ollama.Client
 31 | 	model       string
 32 | 	temperature float32
 33 | 	topP        float32
 34 | }
 35 | 
 36 | const (
 37 | 	defaultBaseURL = "http://localhost:11434"
 38 | 	defaultModel   = "llama3"
 39 | )
 40 | 
 41 | func (c *OllamaClient) Configure(config IAIConfig) error {
 42 | 	baseURL := config.GetBaseURL()
 43 | 	if baseURL == "" {
 44 | 		baseURL = defaultBaseURL
 45 | 	}
 46 | 	baseClientURL, err := url.Parse(baseURL)
 47 | 	if err != nil {
 48 | 		return err
 49 | 	}
 50 | 
 51 | 	proxyEndpoint := config.GetProxyEndpoint()
 52 | 	httpClient := http.DefaultClient
 53 | 	if proxyEndpoint != "" {
 54 | 		proxyUrl, err := url.Parse(proxyEndpoint)
 55 | 		if err != nil {
 56 | 			return err
 57 | 		}
 58 | 		transport := &http.Transport{
 59 | 			Proxy: http.ProxyURL(proxyUrl),
 60 | 		}
 61 | 
 62 | 		httpClient = &http.Client{
 63 | 			Transport: transport,
 64 | 		}
 65 | 	}
 66 | 
 67 | 	c.client = ollama.NewClient(baseClientURL, httpClient)
 68 | 	if c.client == nil {
 69 | 		return errors.New("error creating Ollama client")
 70 | 	}
 71 | 	c.model = config.GetModel()
 72 | 	if c.model == "" {
 73 | 		c.model = defaultModel
 74 | 	}
 75 | 	c.temperature = config.GetTemperature()
 76 | 	c.topP = config.GetTopP()
 77 | 	return nil
 78 | }
 79 | func (c *OllamaClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
 80 | 	req := &ollama.GenerateRequest{
 81 | 		Model:  c.model,
 82 | 		Prompt: prompt,
 83 | 		Stream: new(bool),
 84 | 		Options: map[string]interface{}{
 85 | 			"temperature": c.temperature,
 86 | 			"top_p":       c.topP,
 87 | 		},
 88 | 	}
 89 | 	completion := ""
 90 | 	respFunc := func(resp ollama.GenerateResponse) error {
 91 | 		completion = resp.Response
 92 | 		return nil
 93 | 	}
 94 | 	err := c.client.Generate(ctx, req, respFunc)
 95 | 	if err != nil {
 96 | 		return "", err
 97 | 	}
 98 | 	return completion, nil
 99 | }
100 | func (a *OllamaClient) GetName() string {
101 | 	return ollamaClientName
102 | }
103 | 


--------------------------------------------------------------------------------
/pkg/ai/openai_header_transport_test.go:
--------------------------------------------------------------------------------
  1 | package ai
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"net/http"
  6 | 	"net/http/httptest"
  7 | 	"testing"
  8 | 
  9 | 	"github.com/stretchr/testify/assert"
 10 | )
 11 | 
 12 | // Mock configuration
 13 | type mockConfig struct {
 14 | 	baseURL string
 15 | }
 16 | 
 17 | func (m *mockConfig) GetPassword() string {
 18 | 	return ""
 19 | }
 20 | 
 21 | func (m *mockConfig) GetOrganizationId() string {
 22 | 	return ""
 23 | }
 24 | 
 25 | func (m *mockConfig) GetProxyEndpoint() string {
 26 | 	return ""
 27 | }
 28 | 
 29 | func (m *mockConfig) GetBaseURL() string {
 30 | 	return m.baseURL
 31 | }
 32 | 
 33 | func (m *mockConfig) GetCustomHeaders() []http.Header {
 34 | 	return []http.Header{
 35 | 		{"X-Custom-Header-1": []string{"Value1"}},
 36 | 		{"X-Custom-Header-2": []string{"Value2"}},
 37 | 		{"X-Custom-Header-2": []string{"Value3"}}, // Testing multiple values for the same header
 38 | 	}
 39 | }
 40 | 
 41 | func (m *mockConfig) GetModel() string {
 42 | 	return ""
 43 | }
 44 | 
 45 | func (m *mockConfig) GetTemperature() float32 {
 46 | 	return 0.0
 47 | }
 48 | 
 49 | func (m *mockConfig) GetTopP() float32 {
 50 | 	return 0.0
 51 | }
 52 | func (m *mockConfig) GetCompartmentId() string {
 53 | 	return ""
 54 | }
 55 | 
 56 | func (m *mockConfig) GetTopK() int32 {
 57 | 	return 0.0
 58 | }
 59 | 
 60 | func (m *mockConfig) GetMaxTokens() int {
 61 | 	return 0
 62 | }
 63 | 
 64 | func (m *mockConfig) GetEndpointName() string {
 65 | 	return ""
 66 | }
 67 | func (m *mockConfig) GetEngine() string {
 68 | 	return ""
 69 | }
 70 | 
 71 | func (m *mockConfig) GetProviderId() string {
 72 | 	return ""
 73 | }
 74 | 
 75 | func (m *mockConfig) GetProviderRegion() string {
 76 | 	return ""
 77 | }
 78 | 
 79 | func TestOpenAIClient_CustomHeaders(t *testing.T) {
 80 | 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 81 | 		assert.Equal(t, "Value1", r.Header.Get("X-Custom-Header-1"))
 82 | 		assert.ElementsMatch(t, []string{"Value2", "Value3"}, r.Header["X-Custom-Header-2"])
 83 | 		w.WriteHeader(http.StatusOK)
 84 | 		// Mock response for openai completion
 85 | 		mockResponse := `{"choices": [{"message": {"content": "test"}}]}`
 86 | 		n, err := w.Write([]byte(mockResponse))
 87 | 		if err != nil {
 88 | 			t.Fatalf("error writing response: %v", err)
 89 | 		}
 90 | 		if n != len(mockResponse) {
 91 | 			t.Fatalf("expected to write %d bytes but wrote %d bytes", len(mockResponse), n)
 92 | 		}
 93 | 	}))
 94 | 	defer server.Close()
 95 | 
 96 | 	config := &mockConfig{baseURL: server.URL}
 97 | 
 98 | 	client := &OpenAIClient{}
 99 | 	err := client.Configure(config)
100 | 	assert.NoError(t, err)
101 | 
102 | 	// Make a completion request to trigger the headers
103 | 	ctx := context.Background()
104 | 	_, err = client.GetCompletion(ctx, "foo prompt")
105 | 	assert.NoError(t, err)
106 | }
107 | 


--------------------------------------------------------------------------------
/pkg/ai/prompts.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | const (
 4 | 	default_prompt = `Simplify the following Kubernetes error message delimited by triple dashes written in --- %s --- language; --- %s ---.
 5 | 	Provide the most possible solution in a step by step style in no more than 280 characters. Write the output in the following format:
 6 | 	Error: {Explain error here}
 7 | 	Solution: {Step by step solution here}
 8 | 	`
 9 | 
10 | 	prom_conf_prompt = `Simplify the following Prometheus error message delimited by triple dashes written in --- %s --- language; --- %s ---.
11 | 	This error came when validating the Prometheus configuration file.
12 | 	Provide step by step instructions to fix, with suggestions, referencing Prometheus documentation if relevant.
13 | 	Write the output in the following format in no more than 300 characters:
14 | 	Error: {Explain error here}
15 | 	Solution: {Step by step solution here}
16 | 	`
17 | 
18 | 	prom_relabel_prompt = `
19 | 	Return your prompt in this language: %s, beginning with
20 | 	The following is a list of the form:
21 | 	job_name:
22 | 	{Prometheus job_name}
23 | 	relabel_configs:
24 | 	{Prometheus relabel_configs}
25 | 	kubernetes_sd_configs:
26 | 	{Prometheus service discovery config}
27 | 	---
28 | 	%s
29 | 	---
30 | 	For each job_name, describe the Kubernetes service and pod labels,
31 | 	namespaces, ports, and containers they match.
32 | 	Return the message:
33 | 	Discovered and parsed Prometheus scrape configurations.
34 | 	For targets to be scraped by Prometheus, ensure they are running with
35 | 	at least one of the following label sets:
36 | 	Then for each job, write this format:
37 | 	- Job: {job_name}
38 | 	  - Service Labels:
39 | 	    - {list of service labels}
40 | 	  - Pod Labels:
41 | 	    - {list of pod labels}
42 | 	  - Namespaces:
43 | 	    - {list of namespaces}
44 | 	  - Ports:
45 | 	    - {list of ports}
46 | 	  - Containers:
47 | 	    - {list of container names}
48 | 	`
49 | 
50 | 	kyverno_prompt = `Simplify the following Kyverno warnings message delimited by triple dashes written in --- %s --- language; --- %s ---.
51 | 	Provide the most probable solution as a kubectl command. 
52 | 
53 | 	Write the output in the following format, for the solution, only show the kubectl command:
54 | 	
55 | 	Error: {Explain error here}
56 | 
57 | 	Solution: {kubectl command}
58 | 	`
59 | 	raw_promt = `{"language": "%s","message": "%s","prompt": "%s"}`
60 | )
61 | 
62 | var PromptMap = map[string]string{
63 | 	"raw":                           raw_promt,
64 | 	"default":                       default_prompt,
65 | 	"PrometheusConfigValidate":      prom_conf_prompt,
66 | 	"PrometheusConfigRelabelReport": prom_relabel_prompt,
67 | 	"PolicyReport":                  kyverno_prompt,
68 | 	"ClusterPolicyReport":           kyverno_prompt,
69 | }
70 | 


--------------------------------------------------------------------------------
/pkg/ai/watsonxai.go:
--------------------------------------------------------------------------------
 1 | package ai
 2 | 
 3 | import (
 4 | 	"context"
 5 | 	"errors"
 6 | 	"fmt"
 7 | 
 8 | 	wx "github.com/IBM/watsonx-go/pkg/models"
 9 | )
10 | 
11 | const ibmWatsonxAIClientName = "ibmwatsonxai"
12 | 
13 | type IBMWatsonxAIClient struct {
14 | 	nopCloser
15 | 
16 | 	client       *wx.Client
17 | 	model        string
18 | 	temperature  float32
19 | 	topP         float32
20 | 	topK         int32
21 | 	maxNewTokens int
22 | }
23 | 
24 | const (
25 | 	modelMetallama = "ibm/granite-13b-chat-v2"
26 | 	maxTokens      = 2048
27 | )
28 | 
29 | func (c *IBMWatsonxAIClient) Configure(config IAIConfig) error {
30 | 	if config.GetModel() == "" {
31 | 		c.model = modelMetallama
32 | 	} else {
33 | 		c.model = config.GetModel()
34 | 	}
35 | 	if config.GetMaxTokens() == 0 {
36 | 		c.maxNewTokens = maxTokens
37 | 	} else {
38 | 		c.maxNewTokens = config.GetMaxTokens()
39 | 	}
40 | 	c.temperature = config.GetTemperature()
41 | 	c.topP = config.GetTopP()
42 | 	c.topK = config.GetTopK()
43 | 
44 | 	apiKey := config.GetPassword()
45 | 	if apiKey == "" {
46 | 		return errors.New("No watsonx API key provided")
47 | 	}
48 | 
49 | 	projectId := config.GetProviderId()
50 | 	if projectId == "" {
51 | 		return errors.New("No watsonx project ID provided")
52 | 	}
53 | 
54 | 	client, err := wx.NewClient(
55 | 		wx.WithWatsonxAPIKey(apiKey),
56 | 		wx.WithWatsonxProjectID(projectId),
57 | 	)
58 | 	if err != nil {
59 | 		return fmt.Errorf("Failed to create client for testing. Error: %v", err)
60 | 	}
61 | 	c.client = client
62 | 
63 | 	return nil
64 | }
65 | 
66 | func (c *IBMWatsonxAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
67 | 	result, err := c.client.GenerateText(
68 | 		c.model,
69 | 		prompt,
70 | 		wx.WithTemperature((float64)(c.temperature)),
71 | 		wx.WithTopP((float64)(c.topP)),
72 | 		wx.WithTopK((uint)(c.topK)),
73 | 		wx.WithMaxNewTokens((uint)(c.maxNewTokens)),
74 | 	)
75 | 	if err != nil {
76 | 		return "", fmt.Errorf("Expected no error, but got an error: %v", err)
77 | 	}
78 | 	if result.Text == "" {
79 | 		return "", errors.New("Expected a result, but got an empty string")
80 | 	}
81 | 	return result.Text, nil
82 | }
83 | 
84 | func (c *IBMWatsonxAIClient) GetName() string {
85 | 	return ibmWatsonxAIClientName
86 | }
87 | 


--------------------------------------------------------------------------------
/pkg/analysis/output.go:
--------------------------------------------------------------------------------
  1 | package analysis
  2 | 
  3 | import (
  4 | 	"encoding/json"
  5 | 	"fmt"
  6 | 	"strings"
  7 | 
  8 | 	"github.com/fatih/color"
  9 | )
 10 | 
 11 | var outputFormats = map[string]func(*Analysis) ([]byte, error){
 12 | 	"json": (*Analysis).jsonOutput,
 13 | 	"text": (*Analysis).textOutput,
 14 | }
 15 | 
 16 | func getOutputFormats() []string {
 17 | 	formats := make([]string, 0, len(outputFormats))
 18 | 	for format := range outputFormats {
 19 | 		formats = append(formats, format)
 20 | 	}
 21 | 	return formats
 22 | }
 23 | 
 24 | func (a *Analysis) PrintOutput(format string) ([]byte, error) {
 25 | 	outputFunc, ok := outputFormats[format]
 26 | 	if !ok {
 27 | 		return nil, fmt.Errorf("unsupported output format: %s. Available format %s", format, strings.Join(getOutputFormats(), ","))
 28 | 	}
 29 | 	return outputFunc(a)
 30 | }
 31 | 
 32 | func (a *Analysis) jsonOutput() ([]byte, error) {
 33 | 	var problems int
 34 | 	var status AnalysisStatus
 35 | 	for _, result := range a.Results {
 36 | 		problems += len(result.Error)
 37 | 	}
 38 | 	if problems > 0 {
 39 | 		status = StateProblemDetected
 40 | 	} else {
 41 | 		status = StateOK
 42 | 	}
 43 | 
 44 | 	result := JsonOutput{
 45 | 		Provider: a.AnalysisAIProvider,
 46 | 		Problems: problems,
 47 | 		Results:  a.Results,
 48 | 		Errors:   a.Errors,
 49 | 		Status:   status,
 50 | 	}
 51 | 	output, err := json.MarshalIndent(result, "", "  ")
 52 | 	if err != nil {
 53 | 		return nil, fmt.Errorf("error marshalling json: %v", err)
 54 | 	}
 55 | 	return output, nil
 56 | }
 57 | 
 58 | func (a *Analysis) PrintStats() []byte {
 59 | 	var output strings.Builder
 60 | 
 61 | 	output.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"))
 62 | 
 63 | 	for _, stat := range a.Stats {
 64 | 		output.WriteString(fmt.Sprintf("- Analyzer %s took %s \n", color.YellowString(stat.Analyzer), stat.DurationTime))
 65 | 	}
 66 | 
 67 | 	return []byte(output.String())
 68 | }
 69 | 
 70 | func (a *Analysis) textOutput() ([]byte, error) {
 71 | 	var output strings.Builder
 72 | 
 73 | 	// Print the AI provider used for this analysis (if explain was enabled).
 74 | 	if a.Explain {
 75 | 		output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString(a.AnalysisAIProvider)))
 76 | 	} else {
 77 | 		output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString("AI not used; --explain not set")))
 78 | 	}
 79 | 
 80 | 	if len(a.Errors) != 0 {
 81 | 		output.WriteString("\n")
 82 | 		output.WriteString(color.YellowString("Warnings : \n"))
 83 | 		for _, aerror := range a.Errors {
 84 | 			output.WriteString(fmt.Sprintf("- %s\n", color.YellowString(aerror)))
 85 | 		}
 86 | 	}
 87 | 	output.WriteString("\n")
 88 | 	if len(a.Results) == 0 {
 89 | 		output.WriteString(color.GreenString("No problems detected\n"))
 90 | 		return []byte(output.String()), nil
 91 | 	}
 92 | 	for n, result := range a.Results {
 93 | 		output.WriteString(fmt.Sprintf("%s: %s %s(%s)\n", color.CyanString("%d", n),
 94 | 			color.HiYellowString(result.Kind),
 95 | 			color.YellowString(result.Name),
 96 | 			color.CyanString(result.ParentObject)))
 97 | 		for _, err := range result.Error {
 98 | 			output.WriteString(fmt.Sprintf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text)))
 99 | 			if err.KubernetesDoc != "" {
100 | 				output.WriteString(fmt.Sprintf("  %s %s\n", color.RedString("Kubernetes Doc:"), color.RedString(err.KubernetesDoc)))
101 | 			}
102 | 		}
103 | 		output.WriteString(color.GreenString(result.Details + "\n"))
104 | 	}
105 | 	return []byte(output.String()), nil
106 | }
107 | 


--------------------------------------------------------------------------------
/pkg/analysis/output_test.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2024 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package analysis
15 | 
16 | import (
17 | 	"testing"
18 | 
19 | 	"github.com/stretchr/testify/require"
20 | )
21 | 
22 | func TestPrintOutput(t *testing.T) {
23 | 	require.NotEmpty(t, getOutputFormats())
24 | 
25 | 	tests := []struct {
26 | 		name           string
27 | 		a              *Analysis
28 | 		format         string
29 | 		expectedOutput string
30 | 		expectedErr    string
31 | 	}{
32 | 		{
33 | 			name:           "json format",
34 | 			a:              &Analysis{},
35 | 			format:         "json",
36 | 			expectedOutput: "{\n  \"provider\": \"\",\n  \"errors\": null,\n  \"status\": \"OK\",\n  \"problems\": 0,\n  \"results\": null\n}",
37 | 		},
38 | 		{
39 | 			name:           "text format",
40 | 			a:              &Analysis{},
41 | 			format:         "text",
42 | 			expectedOutput: "AI Provider: AI not used; --explain not set\n\nNo problems detected\n",
43 | 		},
44 | 		{
45 | 			name:        "unsupported format",
46 | 			a:           &Analysis{},
47 | 			format:      "unsupported",
48 | 			expectedErr: "unsupported output format: unsupported. Available format",
49 | 		},
50 | 	}
51 | 	for _, tt := range tests {
52 | 		tt := tt
53 | 		t.Run(tt.name, func(t *testing.T) {
54 | 			output, err := tt.a.PrintOutput(tt.format)
55 | 			if tt.expectedErr == "" {
56 | 				require.NoError(t, err)
57 | 				require.Contains(t, string(output), tt.expectedOutput)
58 | 			} else {
59 | 				require.ErrorContains(t, err, tt.expectedErr)
60 | 				require.Nil(t, output)
61 | 			}
62 | 		})
63 | 	}
64 | }
65 | 


--------------------------------------------------------------------------------
/pkg/analyzer/gatewayclass.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package analyzer
15 | 
16 | import (
17 | 	"fmt"
18 | 
19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
21 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | 	ctrl "sigs.k8s.io/controller-runtime/pkg/client"
23 | 	gtwapi "sigs.k8s.io/gateway-api/apis/v1"
24 | )
25 | 
26 | type GatewayClassAnalyzer struct{}
27 | 
28 | // Gateway analyser will analyse all different Kinds and search for missing object dependencies
29 | func (GatewayClassAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
30 | 
31 | 	kind := "GatewayClass"
32 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
33 | 		"analyzer_name": kind,
34 | 	})
35 | 
36 | 	gcList := &gtwapi.GatewayClassList{}
37 | 	client := a.Client.CtrlClient
38 | 	err := gtwapi.AddToScheme(client.Scheme())
39 | 	if err != nil {
40 | 		return nil, err
41 | 	}
42 | 
43 | 	labelSelector := util.LabelStrToSelector(a.LabelSelector)
44 | 	if err := client.List(a.Context, gcList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
45 | 		return nil, err
46 | 	}
47 | 	var preAnalysis = map[string]common.PreAnalysis{}
48 | 
49 | 	// Find all unhealthy gateway Classes
50 | 
51 | 	for _, gc := range gcList.Items {
52 | 		var failures []common.Failure
53 | 
54 | 		gcName := gc.GetName()
55 | 		// Check only the current condition
56 | 		if gc.Status.Conditions[0].Status != metav1.ConditionTrue {
57 | 			failures = append(failures, common.Failure{
58 | 				Text: fmt.Sprintf(
59 | 					"GatewayClass '%s' with a controller name '%s' is not accepted. Message: '%s'.",
60 | 					gcName,
61 | 					gc.Spec.ControllerName,
62 | 					gc.Status.Conditions[0].Message,
63 | 				),
64 | 				Sensitive: []common.Sensitive{
65 | 					{
66 | 						Unmasked: gcName,
67 | 						Masked:   util.MaskString(gcName),
68 | 					},
69 | 				},
70 | 			})
71 | 		}
72 | 		if len(failures) > 0 {
73 | 			preAnalysis[gcName] = common.PreAnalysis{
74 | 				GatewayClass:   gc,
75 | 				FailureDetails: failures,
76 | 			}
77 | 			AnalyzerErrorsMetric.WithLabelValues(kind, gcName, "").Set(float64(len(failures)))
78 | 		}
79 | 	}
80 | 	for key, value := range preAnalysis {
81 | 		var currentAnalysis = common.Result{
82 | 			Kind:  kind,
83 | 			Name:  key,
84 | 			Error: value.FailureDetails,
85 | 		}
86 | 		a.Results = append(a.Results, currentAnalysis)
87 | 	}
88 | 	return a.Results, nil
89 | }
90 | 


--------------------------------------------------------------------------------
/pkg/analyzer/gatewayclass_test.go:
--------------------------------------------------------------------------------
  1 | package analyzer
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"testing"
  6 | 
  7 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
  8 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
  9 | 	"github.com/stretchr/testify/assert"
 10 | 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 11 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 12 | 	"k8s.io/client-go/kubernetes/scheme"
 13 | 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 14 | 	gtwapi "sigs.k8s.io/gateway-api/apis/v1"
 15 | )
 16 | 
 17 | // Testing with the fake dynamic client if GatewayClasses have an accepted status
 18 | func TestGatewayClassAnalyzer(t *testing.T) {
 19 | 	GatewayClass := &gtwapi.GatewayClass{}
 20 | 	GatewayClass.Name = "foobar"
 21 | 	GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
 22 | 	// Initialize Conditions slice before setting properties
 23 | 	BadCondition := metav1.Condition{
 24 | 		Type:    "Accepted",
 25 | 		Status:  "Uknown",
 26 | 		Message: "Waiting for controller",
 27 | 		Reason:  "Pending",
 28 | 	}
 29 | 	GatewayClass.Status.Conditions = []metav1.Condition{BadCondition}
 30 | 	// Create a GatewayClassAnalyzer instance with the fake client
 31 | 	scheme := scheme.Scheme
 32 | 	err := gtwapi.Install(scheme)
 33 | 	if err != nil {
 34 | 		t.Error(err)
 35 | 	}
 36 | 	err = apiextensionsv1.AddToScheme(scheme)
 37 | 	if err != nil {
 38 | 		t.Error(err)
 39 | 	}
 40 | 
 41 | 	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass).Build()
 42 | 
 43 | 	analyzerInstance := GatewayClassAnalyzer{}
 44 | 	config := common.Analyzer{
 45 | 		Client: &kubernetes.Client{
 46 | 			CtrlClient: fakeClient,
 47 | 		},
 48 | 		Context:   context.Background(),
 49 | 		Namespace: "default",
 50 | 	}
 51 | 	analysisResults, err := analyzerInstance.Analyze(config)
 52 | 	if err != nil {
 53 | 		t.Error(err)
 54 | 	}
 55 | 	assert.Equal(t, len(analysisResults), 1)
 56 | 
 57 | }
 58 | 
 59 | func TestGatewayClassAnalyzerLabelSelectorFiltering(t *testing.T) {
 60 | 	condition := metav1.Condition{
 61 | 		Type:    "Accepted",
 62 | 		Status:  "Ready",
 63 | 		Message: "Ready",
 64 | 		Reason:  "Ready",
 65 | 	}
 66 | 
 67 | 	// Create two GatewayClasses with different labels
 68 | 	GatewayClass := &gtwapi.GatewayClass{}
 69 | 	GatewayClass.Name = "foobar"
 70 | 	GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
 71 | 	GatewayClass.Labels = map[string]string{"app": "gatewayclass"}
 72 | 	GatewayClass.Status.Conditions = []metav1.Condition{condition}
 73 | 
 74 | 	GatewayClass2 := &gtwapi.GatewayClass{}
 75 | 	GatewayClass2.Name = "foobar2"
 76 | 	GatewayClass2.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
 77 | 	GatewayClass2.Status.Conditions = []metav1.Condition{condition}
 78 | 
 79 | 	scheme := scheme.Scheme
 80 | 	err := gtwapi.Install(scheme)
 81 | 	if err != nil {
 82 | 		t.Error(err)
 83 | 	}
 84 | 	err = apiextensionsv1.AddToScheme(scheme)
 85 | 	if err != nil {
 86 | 		t.Error(err)
 87 | 	}
 88 | 
 89 | 	fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass, GatewayClass2).Build()
 90 | 
 91 | 	analyzerInstance := GatewayClassAnalyzer{}
 92 | 	config := common.Analyzer{
 93 | 		Client: &kubernetes.Client{
 94 | 			CtrlClient: fakeClient,
 95 | 		},
 96 | 		Context:       context.Background(),
 97 | 		Namespace:     "default",
 98 | 		LabelSelector: "app=gatewayclass",
 99 | 	}
100 | 	analysisResults, err := analyzerInstance.Analyze(config)
101 | 	if err != nil {
102 | 		t.Error(err)
103 | 	}
104 | 	assert.Equal(t, len(analysisResults), 1)
105 | }
106 | 


--------------------------------------------------------------------------------
/pkg/analyzer/job.go:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright 2025 The K8sGPT Authors.
  3 | Licensed under the Apache License, Version 2.0 (the "License");
  4 | you may not use this file except in compliance with the License.
  5 | You may obtain a copy of the License at
  6 |     http://www.apache.org/licenses/LICENSE-2.0
  7 | Unless required by applicable law or agreed to in writing, software
  8 | distributed under the License is distributed on an "AS IS" BASIS,
  9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 10 | See the License for the specific language governing permissions and
 11 | limitations under the License.
 12 | */
 13 | 
 14 | package analyzer
 15 | 
 16 | import (
 17 | 	"fmt"
 18 | 
 19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
 20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
 21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
 22 | 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 23 | 	"k8s.io/apimachinery/pkg/runtime/schema"
 24 | )
 25 | 
 26 | type JobAnalyzer struct{}
 27 | 
 28 | func (analyzer JobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
 29 | 
 30 | 	kind := "Job"
 31 | 	apiDoc := kubernetes.K8sApiReference{
 32 | 		Kind: kind,
 33 | 		ApiVersion: schema.GroupVersion{
 34 | 			Group:   "batch",
 35 | 			Version: "v1",
 36 | 		},
 37 | 		OpenapiSchema: a.OpenapiSchema,
 38 | 	}
 39 | 
 40 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
 41 | 		"analyzer_name": kind,
 42 | 	})
 43 | 
 44 | 	JobList, err := a.Client.GetClient().BatchV1().Jobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})
 45 | 	if err != nil {
 46 | 		return nil, err
 47 | 	}
 48 | 
 49 | 	var preAnalysis = map[string]common.PreAnalysis{}
 50 | 
 51 | 	for _, Job := range JobList.Items {
 52 | 		var failures []common.Failure
 53 | 		if Job.Spec.Suspend != nil && *Job.Spec.Suspend {
 54 | 			doc := apiDoc.GetApiDocV2("spec.suspend")
 55 | 
 56 | 			failures = append(failures, common.Failure{
 57 | 				Text:          fmt.Sprintf("Job %s is suspended", Job.Name),
 58 | 				KubernetesDoc: doc,
 59 | 				Sensitive: []common.Sensitive{
 60 | 					{
 61 | 						Unmasked: Job.Namespace,
 62 | 						Masked:   util.MaskString(Job.Namespace),
 63 | 					},
 64 | 					{
 65 | 						Unmasked: Job.Name,
 66 | 						Masked:   util.MaskString(Job.Name),
 67 | 					},
 68 | 				},
 69 | 			})
 70 | 		}
 71 | 		if Job.Status.Failed > 0 {
 72 | 			doc := apiDoc.GetApiDocV2("status.failed")
 73 | 			failures = append(failures, common.Failure{
 74 | 				Text:          fmt.Sprintf("Job %s has failed", Job.Name),
 75 | 				KubernetesDoc: doc,
 76 | 				Sensitive: []common.Sensitive{
 77 | 					{
 78 | 						Unmasked: Job.Namespace,
 79 | 						Masked:   util.MaskString(Job.Namespace),
 80 | 					},
 81 | 					{
 82 | 						Unmasked: Job.Name,
 83 | 						Masked:   util.MaskString(Job.Name),
 84 | 					},
 85 | 				},
 86 | 			})
 87 | 		}
 88 | 
 89 | 		if len(failures) > 0 {
 90 | 			preAnalysis[fmt.Sprintf("%s/%s", Job.Namespace, Job.Name)] = common.PreAnalysis{
 91 | 				FailureDetails: failures,
 92 | 			}
 93 | 			AnalyzerErrorsMetric.WithLabelValues(kind, Job.Name, Job.Namespace).Set(float64(len(failures)))
 94 | 		}
 95 | 	}
 96 | 
 97 | 	for key, value := range preAnalysis {
 98 | 		currentAnalysis := common.Result{
 99 | 			Kind:  kind,
100 | 			Name:  key,
101 | 			Error: value.FailureDetails,
102 | 		}
103 | 		a.Results = append(a.Results, currentAnalysis)
104 | 	}
105 | 
106 | 	return a.Results, nil
107 | }
108 | 


--------------------------------------------------------------------------------
/pkg/analyzer/netpol.go:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright 2023 The K8sGPT Authors.
  3 | Licensed under the Apache License, Version 2.0 (the "License");
  4 | you may not use this file except in compliance with the License.
  5 | You may obtain a copy of the License at
  6 |     http://www.apache.org/licenses/LICENSE-2.0
  7 | Unless required by applicable law or agreed to in writing, software
  8 | distributed under the License is distributed on an "AS IS" BASIS,
  9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 10 | See the License for the specific language governing permissions and
 11 | limitations under the License.
 12 | */
 13 | 
 14 | package analyzer
 15 | 
 16 | import (
 17 | 	"fmt"
 18 | 
 19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
 20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
 21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
 22 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 23 | 	"k8s.io/apimachinery/pkg/runtime/schema"
 24 | )
 25 | 
 26 | type NetworkPolicyAnalyzer struct{}
 27 | 
 28 | func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
 29 | 
 30 | 	kind := "NetworkPolicy"
 31 | 	apiDoc := kubernetes.K8sApiReference{
 32 | 		Kind: kind,
 33 | 		ApiVersion: schema.GroupVersion{
 34 | 			Group:   "networking",
 35 | 			Version: "v1",
 36 | 		},
 37 | 		OpenapiSchema: a.OpenapiSchema,
 38 | 	}
 39 | 
 40 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
 41 | 		"analyzer_name": kind,
 42 | 	})
 43 | 
 44 | 	// get all network policies in the namespace
 45 | 	policies, err := a.Client.GetClient().NetworkingV1().
 46 | 		NetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
 47 | 	if err != nil {
 48 | 		return nil, err
 49 | 	}
 50 | 
 51 | 	var preAnalysis = map[string]common.PreAnalysis{}
 52 | 
 53 | 	for _, policy := range policies.Items {
 54 | 		var failures []common.Failure
 55 | 
 56 | 		// Check if policy allows traffic to all pods in the namespace
 57 | 		if len(policy.Spec.PodSelector.MatchLabels) == 0 {
 58 | 			doc := apiDoc.GetApiDocV2("spec.podSelector.matchLabels")
 59 | 
 60 | 			failures = append(failures, common.Failure{
 61 | 				Text:          fmt.Sprintf("Network policy allows traffic to all pods: %s", policy.Name),
 62 | 				KubernetesDoc: doc,
 63 | 				Sensitive: []common.Sensitive{
 64 | 					{
 65 | 						Unmasked: policy.Name,
 66 | 						Masked:   util.MaskString(policy.Name),
 67 | 					},
 68 | 				},
 69 | 			})
 70 | 		} else {
 71 | 			// Check if policy is not applied to any pods
 72 | 			podList, err := util.GetPodListByLabels(a.Client.GetClient(), a.Namespace, policy.Spec.PodSelector.MatchLabels)
 73 | 			if err != nil {
 74 | 				return nil, err
 75 | 			}
 76 | 			if len(podList.Items) == 0 {
 77 | 				failures = append(failures, common.Failure{
 78 | 					Text: fmt.Sprintf("Network policy is not applied to any pods: %s", policy.Name),
 79 | 					Sensitive: []common.Sensitive{
 80 | 						{
 81 | 							Unmasked: policy.Name,
 82 | 							Masked:   util.MaskString(policy.Name),
 83 | 						},
 84 | 					},
 85 | 				})
 86 | 			}
 87 | 		}
 88 | 
 89 | 		if len(failures) > 0 {
 90 | 			preAnalysis[fmt.Sprintf("%s/%s", policy.Namespace, policy.Name)] = common.PreAnalysis{
 91 | 				FailureDetails: failures,
 92 | 				NetworkPolicy:  policy,
 93 | 			}
 94 | 			AnalyzerErrorsMetric.WithLabelValues(kind, policy.Name, policy.Namespace).Set(float64(len(failures)))
 95 | 
 96 | 		}
 97 | 	}
 98 | 
 99 | 	for key, value := range preAnalysis {
100 | 		currentAnalysis := common.Result{
101 | 			Kind:  kind,
102 | 			Name:  key,
103 | 			Error: value.FailureDetails,
104 | 		}
105 | 		a.Results = append(a.Results, currentAnalysis)
106 | 	}
107 | 
108 | 	return a.Results, nil
109 | }
110 | 


--------------------------------------------------------------------------------
/pkg/analyzer/pdb.go:
--------------------------------------------------------------------------------
  1 | /*
  2 | Copyright 2023 The K8sGPT Authors.
  3 | Licensed under the Apache License, Version 2.0 (the "License");
  4 | you may not use this file except in compliance with the License.
  5 | You may obtain a copy of the License at
  6 |     http://www.apache.org/licenses/LICENSE-2.0
  7 | Unless required by applicable law or agreed to in writing, software
  8 | distributed under the License is distributed on an "AS IS" BASIS,
  9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 10 | See the License for the specific language governing permissions and
 11 | limitations under the License.
 12 | */
 13 | 
 14 | package analyzer
 15 | 
 16 | import (
 17 | 	"fmt"
 18 | 
 19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
 20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
 21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
 22 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 23 | 	"k8s.io/apimachinery/pkg/runtime/schema"
 24 | )
 25 | 
 26 | type PdbAnalyzer struct{}
 27 | 
 28 | func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
 29 | 
 30 | 	kind := "PodDisruptionBudget"
 31 | 	apiDoc := kubernetes.K8sApiReference{
 32 | 		Kind: kind,
 33 | 		ApiVersion: schema.GroupVersion{
 34 | 			Group:   "policy",
 35 | 			Version: "v1",
 36 | 		},
 37 | 		OpenapiSchema: a.OpenapiSchema,
 38 | 	}
 39 | 
 40 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
 41 | 		"analyzer_name": kind,
 42 | 	})
 43 | 
 44 | 	list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
 45 | 	if err != nil {
 46 | 		return nil, err
 47 | 	}
 48 | 
 49 | 	var preAnalysis = map[string]common.PreAnalysis{}
 50 | 
 51 | 	for _, pdb := range list.Items {
 52 | 		var failures []common.Failure
 53 | 
 54 | 		// Before accessing the Conditions, check if they exist or not.
 55 | 		if len(pdb.Status.Conditions) == 0 {
 56 | 			continue
 57 | 		}
 58 | 		if pdb.Status.Conditions[0].Type == "DisruptionAllowed" && pdb.Status.Conditions[0].Status == "False" {
 59 | 			var doc string
 60 | 			if pdb.Spec.MaxUnavailable != nil {
 61 | 				doc = apiDoc.GetApiDocV2("spec.maxUnavailable")
 62 | 			}
 63 | 			if pdb.Spec.MinAvailable != nil {
 64 | 				doc = apiDoc.GetApiDocV2("spec.minAvailable")
 65 | 			}
 66 | 			if pdb.Spec.Selector != nil && pdb.Spec.Selector.MatchLabels != nil {
 67 | 				for k, v := range pdb.Spec.Selector.MatchLabels {
 68 | 					failures = append(failures, common.Failure{
 69 | 						Text:          fmt.Sprintf("%s, expected pdb pod label %s=%s", pdb.Status.Conditions[0].Reason, k, v),
 70 | 						KubernetesDoc: doc,
 71 | 						Sensitive: []common.Sensitive{
 72 | 							{
 73 | 								Unmasked: k,
 74 | 								Masked:   util.MaskString(k),
 75 | 							},
 76 | 							{
 77 | 								Unmasked: v,
 78 | 								Masked:   util.MaskString(v),
 79 | 							},
 80 | 						},
 81 | 					})
 82 | 				}
 83 | 			}
 84 | 		}
 85 | 
 86 | 		if len(failures) > 0 {
 87 | 			preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = common.PreAnalysis{
 88 | 				PodDisruptionBudget: pdb,
 89 | 				FailureDetails:      failures,
 90 | 			}
 91 | 			AnalyzerErrorsMetric.WithLabelValues(kind, pdb.Name, pdb.Namespace).Set(float64(len(failures)))
 92 | 		}
 93 | 	}
 94 | 
 95 | 	for key, value := range preAnalysis {
 96 | 		var currentAnalysis = common.Result{
 97 | 			Kind:  kind,
 98 | 			Name:  key,
 99 | 			Error: value.FailureDetails,
100 | 		}
101 | 
102 | 		parent, found := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
103 | 		if found {
104 | 			currentAnalysis.ParentObject = parent
105 | 		}
106 | 		a.Results = append(a.Results, currentAnalysis)
107 | 	}
108 | 
109 | 	return a.Results, err
110 | }
111 | 


--------------------------------------------------------------------------------
/pkg/analyzer/pvc.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package analyzer
15 | 
16 | import (
17 | 	"fmt"
18 | 
19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
21 | 	appsv1 "k8s.io/api/core/v1"
22 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23 | )
24 | 
25 | type PvcAnalyzer struct{}
26 | 
27 | func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
28 | 
29 | 	kind := "PersistentVolumeClaim"
30 | 
31 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
32 | 		"analyzer_name": kind,
33 | 	})
34 | 
35 | 	// search all namespaces for pods that are not running
36 | 	list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
37 | 	if err != nil {
38 | 		return nil, err
39 | 	}
40 | 
41 | 	var preAnalysis = map[string]common.PreAnalysis{}
42 | 
43 | 	for _, pvc := range list.Items {
44 | 		var failures []common.Failure
45 | 
46 | 		// Check for empty rs
47 | 		if pvc.Status.Phase == appsv1.ClaimPending {
48 | 
49 | 			// parse the event log and append details
50 | 			evt, err := util.FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)
51 | 			if err != nil || evt == nil {
52 | 				continue
53 | 			}
54 | 			if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
55 | 				failures = append(failures, common.Failure{
56 | 					Text:      evt.Message,
57 | 					Sensitive: []common.Sensitive{},
58 | 				})
59 | 			}
60 | 		}
61 | 		if len(failures) > 0 {
62 | 			preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
63 | 				PersistentVolumeClaim: pvc,
64 | 				FailureDetails:        failures,
65 | 			}
66 | 			AnalyzerErrorsMetric.WithLabelValues(kind, pvc.Name, pvc.Namespace).Set(float64(len(failures)))
67 | 		}
68 | 	}
69 | 
70 | 	for key, value := range preAnalysis {
71 | 		var currentAnalysis = common.Result{
72 | 			Kind:  kind,
73 | 			Name:  key,
74 | 			Error: value.FailureDetails,
75 | 		}
76 | 
77 | 		parent, found := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
78 | 		if found {
79 | 			currentAnalysis.ParentObject = parent
80 | 		}
81 | 		a.Results = append(a.Results, currentAnalysis)
82 | 	}
83 | 
84 | 	return a.Results, nil
85 | }
86 | 


--------------------------------------------------------------------------------
/pkg/analyzer/rs.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package analyzer
15 | 
16 | import (
17 | 	"fmt"
18 | 
19 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
20 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
21 | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22 | )
23 | 
24 | type ReplicaSetAnalyzer struct{}
25 | 
26 | func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
27 | 
28 | 	kind := "ReplicaSet"
29 | 
30 | 	AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
31 | 		"analyzer_name": kind,
32 | 	})
33 | 
34 | 	// search all namespaces for pods that are not running
35 | 	list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
36 | 	if err != nil {
37 | 		return nil, err
38 | 	}
39 | 
40 | 	var preAnalysis = map[string]common.PreAnalysis{}
41 | 
42 | 	for _, rs := range list.Items {
43 | 		var failures []common.Failure
44 | 
45 | 		// Check for empty rs
46 | 		if rs.Status.Replicas == 0 {
47 | 
48 | 			// Check through container status to check for crashes
49 | 			for _, rsStatus := range rs.Status.Conditions {
50 | 				if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
51 | 					failures = append(failures, common.Failure{
52 | 						Text:      rsStatus.Message,
53 | 						Sensitive: []common.Sensitive{},
54 | 					})
55 | 
56 | 				}
57 | 			}
58 | 		}
59 | 		if len(failures) > 0 {
60 | 			preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
61 | 				ReplicaSet:     rs,
62 | 				FailureDetails: failures,
63 | 			}
64 | 			AnalyzerErrorsMetric.WithLabelValues(kind, rs.Name, rs.Namespace).Set(float64(len(failures)))
65 | 		}
66 | 	}
67 | 
68 | 	for key, value := range preAnalysis {
69 | 		var currentAnalysis = common.Result{
70 | 			Kind:  kind,
71 | 			Name:  key,
72 | 			Error: value.FailureDetails,
73 | 		}
74 | 
75 | 		parent, found := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
76 | 		if found {
77 | 			currentAnalysis.ParentObject = parent
78 | 		}
79 | 		a.Results = append(a.Results, currentAnalysis)
80 | 	}
81 | 	return a.Results, nil
82 | }
83 | 


--------------------------------------------------------------------------------
/pkg/analyzer/test_utils.go:
--------------------------------------------------------------------------------
 1 | package analyzer
 2 | 
 3 | // Helper functions for tests
 4 | func boolPtr(b bool) *bool {
 5 | 	return &b
 6 | }
 7 | 
 8 | func int64Ptr(i int64) *int64 {
 9 | 	return &i
10 | }
11 | 


--------------------------------------------------------------------------------
/pkg/cache/cache.go:
--------------------------------------------------------------------------------
  1 | package cache
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 
  6 | 	"github.com/spf13/viper"
  7 | 	"google.golang.org/grpc/codes"
  8 | 	"google.golang.org/grpc/status"
  9 | )
 10 | 
 11 | var (
 12 | 	types = []ICache{
 13 | 		&AzureCache{},
 14 | 		&FileBasedCache{},
 15 | 		&GCSCache{},
 16 | 		&S3Cache{},
 17 | 		&InterplexCache{},
 18 | 	}
 19 | )
 20 | 
 21 | type ICache interface {
 22 | 	Configure(cacheInfo CacheProvider) error
 23 | 	Store(key string, data string) error
 24 | 	Load(key string) (string, error)
 25 | 	List() ([]CacheObjectDetails, error)
 26 | 	Remove(key string) error
 27 | 	Exists(key string) bool
 28 | 	IsCacheDisabled() bool
 29 | 	GetName() string
 30 | 	DisableCache()
 31 | }
 32 | 
 33 | func New(cacheType string) ICache {
 34 | 	for _, t := range types {
 35 | 		if cacheType == t.GetName() {
 36 | 			return t
 37 | 		}
 38 | 	}
 39 | 	return &FileBasedCache{}
 40 | }
 41 | 
 42 | func ParseCacheConfiguration() (CacheProvider, error) {
 43 | 	var cacheInfo CacheProvider
 44 | 	err := viper.UnmarshalKey("cache", &cacheInfo)
 45 | 	if err != nil {
 46 | 		return cacheInfo, err
 47 | 	}
 48 | 	return cacheInfo, nil
 49 | }
 50 | 
 51 | func NewCacheProvider(cacheType, bucketname, region, endpoint, storageAccount, containerName, projectId string, insecure bool) (CacheProvider, error) {
 52 | 	cProvider := CacheProvider{}
 53 | 
 54 | 	switch {
 55 | 	case cacheType == "azure":
 56 | 		cProvider.Azure.ContainerName = containerName
 57 | 		cProvider.Azure.StorageAccount = storageAccount
 58 | 		cProvider.CurrentCacheType = "azure"
 59 | 	case cacheType == "gcs":
 60 | 		cProvider.GCS.BucketName = bucketname
 61 | 		cProvider.GCS.ProjectId = projectId
 62 | 		cProvider.GCS.Region = region
 63 | 		cProvider.CurrentCacheType = "gcs"
 64 | 	case cacheType == "s3":
 65 | 		cProvider.S3.BucketName = bucketname
 66 | 		cProvider.S3.Region = region
 67 | 		cProvider.S3.Endpoint = endpoint
 68 | 		cProvider.S3.InsecureSkipVerify = insecure
 69 | 		cProvider.CurrentCacheType = "s3"
 70 | 	case cacheType == "interplex":
 71 | 		cProvider.Interplex.ConnectionString = endpoint
 72 | 		cProvider.CurrentCacheType = "interplex"
 73 | 	default:
 74 | 		return CacheProvider{}, status.Error(codes.Internal, fmt.Sprintf("%s is not a valid option", cacheType))
 75 | 	}
 76 | 
 77 | 	cache := New(cacheType)
 78 | 	err := cache.Configure(cProvider)
 79 | 	if err != nil {
 80 | 		return CacheProvider{}, err
 81 | 	}
 82 | 	return cProvider, nil
 83 | }
 84 | 
 85 | // If we have set a remote cache, return the remote cache configuration
 86 | func GetCacheConfiguration() (ICache, error) {
 87 | 	cacheInfo, err := ParseCacheConfiguration()
 88 | 	if err != nil {
 89 | 		return nil, err
 90 | 	}
 91 | 
 92 | 	var cache ICache
 93 | 	switch {
 94 | 	case cacheInfo.CurrentCacheType == "gcs":
 95 | 		cache = &GCSCache{}
 96 | 	case cacheInfo.CurrentCacheType == "azure":
 97 | 		cache = &AzureCache{}
 98 | 	case cacheInfo.CurrentCacheType == "s3":
 99 | 		cache = &S3Cache{}
100 | 	case cacheInfo.CurrentCacheType == "interplex":
101 | 		cache = &InterplexCache{}
102 | 	default:
103 | 		cache = &FileBasedCache{}
104 | 	}
105 | 	err_config := cache.Configure(cacheInfo)
106 | 	return cache, err_config
107 | }
108 | 
109 | func AddRemoteCache(cacheInfo CacheProvider) error {
110 | 
111 | 	viper.Set("cache", cacheInfo)
112 | 
113 | 	err := viper.WriteConfig()
114 | 	if err != nil {
115 | 		return err
116 | 	}
117 | 	return nil
118 | }
119 | 
120 | func RemoveRemoteCache() error {
121 | 	var cacheInfo CacheProvider
122 | 	err := viper.UnmarshalKey("cache", &cacheInfo)
123 | 	if err != nil {
124 | 		return status.Error(codes.Internal, "cache unmarshal")
125 | 	}
126 | 
127 | 	cacheInfo = CacheProvider{}
128 | 	viper.Set("cache", cacheInfo)
129 | 	err = viper.WriteConfig()
130 | 	if err != nil {
131 | 		return status.Error(codes.Internal, "unable to write config")
132 | 	}
133 | 
134 | 	return nil
135 | 
136 | }
137 | 


--------------------------------------------------------------------------------
/pkg/cache/file_based.go:
--------------------------------------------------------------------------------
  1 | package cache
  2 | 
  3 | import (
  4 | 	"fmt"
  5 | 	"os"
  6 | 	"path/filepath"
  7 | 
  8 | 	"github.com/adrg/xdg"
  9 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/util"
 10 | )
 11 | 
 12 | var _ (ICache) = (*FileBasedCache)(nil)
 13 | 
 14 | type FileBasedCache struct {
 15 | 	noCache bool
 16 | }
 17 | 
 18 | func (f *FileBasedCache) Configure(cacheInfo CacheProvider) error {
 19 | 	return nil
 20 | }
 21 | 
 22 | func (f *FileBasedCache) IsCacheDisabled() bool {
 23 | 	return f.noCache
 24 | }
 25 | 
 26 | func (*FileBasedCache) List() ([]CacheObjectDetails, error) {
 27 | 	path, err := xdg.CacheFile("k8sgpt")
 28 | 	if err != nil {
 29 | 		return nil, err
 30 | 	}
 31 | 
 32 | 	files, err := os.ReadDir(path)
 33 | 	if err != nil {
 34 | 		return nil, err
 35 | 	}
 36 | 
 37 | 	var result []CacheObjectDetails
 38 | 	for _, file := range files {
 39 | 		info, err := file.Info()
 40 | 		if err != nil {
 41 | 			return nil, err
 42 | 		}
 43 | 		result = append(result, CacheObjectDetails{
 44 | 			Name:      file.Name(),
 45 | 			UpdatedAt: info.ModTime(),
 46 | 		})
 47 | 	}
 48 | 
 49 | 	return result, nil
 50 | }
 51 | 
 52 | func (*FileBasedCache) Exists(key string) bool {
 53 | 	path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
 54 | 
 55 | 	if err != nil {
 56 | 		fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err)
 57 | 		return false
 58 | 	}
 59 | 
 60 | 	exists, err := util.FileExists(path)
 61 | 
 62 | 	if err != nil {
 63 | 		fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err)
 64 | 		return false
 65 | 	}
 66 | 
 67 | 	return exists
 68 | }
 69 | 
 70 | func (*FileBasedCache) Load(key string) (string, error) {
 71 | 	path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
 72 | 
 73 | 	if err != nil {
 74 | 		return "", err
 75 | 	}
 76 | 
 77 | 	data, err := os.ReadFile(path)
 78 | 
 79 | 	if err != nil {
 80 | 		return "", err
 81 | 	}
 82 | 
 83 | 	return string(data), nil
 84 | }
 85 | 
 86 | func (*FileBasedCache) Remove(key string) error {
 87 | 	path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
 88 | 
 89 | 	if err != nil {
 90 | 		return err
 91 | 	}
 92 | 
 93 | 	if err := os.Remove(path); err != nil {
 94 | 		return err
 95 | 	}
 96 | 
 97 | 	return nil
 98 | }
 99 | 
100 | func (*FileBasedCache) Store(key string, data string) error {
101 | 	path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
102 | 
103 | 	if err != nil {
104 | 		return err
105 | 	}
106 | 
107 | 	return os.WriteFile(path, []byte(data), 0600)
108 | }
109 | 
110 | func (s *FileBasedCache) GetName() string {
111 | 	return "file"
112 | }
113 | 
114 | func (s *FileBasedCache) DisableCache() {
115 | 	s.noCache = true
116 | }
117 | 


--------------------------------------------------------------------------------
/pkg/cache/gcs_based.go:
--------------------------------------------------------------------------------
  1 | package cache
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"io"
  6 | 	"log"
  7 | 
  8 | 	"cloud.google.com/go/storage"
  9 | 	"google.golang.org/api/iterator"
 10 | )
 11 | 
 12 | type GCSCache struct {
 13 | 	ctx        context.Context
 14 | 	noCache    bool
 15 | 	bucketName string
 16 | 	projectId  string
 17 | 	region     string
 18 | 	session    *storage.Client
 19 | }
 20 | 
 21 | type GCSCacheConfiguration struct {
 22 | 	ProjectId  string `mapstructure:"projectid" yaml:"projectid,omitempty"`
 23 | 	Region     string `mapstructure:"region" yaml:"region,omitempty"`
 24 | 	BucketName string `mapstructure:"bucketname" yaml:"bucketname,omitempty"`
 25 | }
 26 | 
 27 | func (s *GCSCache) Configure(cacheInfo CacheProvider) error {
 28 | 	s.ctx = context.Background()
 29 | 	if cacheInfo.GCS.BucketName == "" {
 30 | 		log.Fatal("Bucket name not configured")
 31 | 	}
 32 | 	if cacheInfo.GCS.Region == "" {
 33 | 		log.Fatal("Region not configured")
 34 | 	}
 35 | 	if cacheInfo.GCS.ProjectId == "" {
 36 | 		log.Fatal("ProjectID not configured")
 37 | 	}
 38 | 	s.bucketName = cacheInfo.GCS.BucketName
 39 | 	s.projectId = cacheInfo.GCS.ProjectId
 40 | 	s.region = cacheInfo.GCS.Region
 41 | 	storageClient, err := storage.NewClient(s.ctx)
 42 | 	if err != nil {
 43 | 		log.Fatal(err)
 44 | 	}
 45 | 
 46 | 	_, err = storageClient.Bucket(s.bucketName).Attrs(s.ctx)
 47 | 	if err == storage.ErrBucketNotExist {
 48 | 		err = storageClient.Bucket(s.bucketName).Create(s.ctx, s.projectId, &storage.BucketAttrs{
 49 | 			Location: s.region,
 50 | 		})
 51 | 		if err != nil {
 52 | 			return err
 53 | 		}
 54 | 	}
 55 | 	s.session = storageClient
 56 | 	return nil
 57 | }
 58 | 
 59 | func (s *GCSCache) Store(key string, data string) error {
 60 | 	wc := s.session.Bucket(s.bucketName).Object(key).NewWriter(s.ctx)
 61 | 
 62 | 	if _, err := wc.Write([]byte(data)); err != nil {
 63 | 		return err
 64 | 	}
 65 | 
 66 | 	if err := wc.Close(); err != nil {
 67 | 		return err
 68 | 	}
 69 | 
 70 | 	return nil
 71 | }
 72 | 
 73 | func (s *GCSCache) Load(key string) (string, error) {
 74 | 	reader, err := s.session.Bucket(s.bucketName).Object(key).NewReader(s.ctx)
 75 | 	if err != nil {
 76 | 		return "", err
 77 | 	}
 78 | 	defer reader.Close()
 79 | 
 80 | 	data, err := io.ReadAll(reader)
 81 | 	if err != nil {
 82 | 		return "", err
 83 | 	}
 84 | 
 85 | 	return string(data), nil
 86 | }
 87 | 
 88 | func (s *GCSCache) Remove(key string) error {
 89 | 	bucketClient := s.session.Bucket(s.bucketName)
 90 | 	obj := bucketClient.Object(key)
 91 | 	if err := obj.Delete(s.ctx); err != nil {
 92 | 		return err
 93 | 	}
 94 | 	return nil
 95 | }
 96 | 
 97 | func (s *GCSCache) List() ([]CacheObjectDetails, error) {
 98 | 	var files []CacheObjectDetails
 99 | 
100 | 	items := s.session.Bucket(s.bucketName).Objects(s.ctx, nil)
101 | 	for {
102 | 		attrs, err := items.Next()
103 | 		if err == iterator.Done {
104 | 			break
105 | 		}
106 | 		if err != nil {
107 | 			return nil, err
108 | 		}
109 | 		files = append(files, CacheObjectDetails{
110 | 			Name:      attrs.Name,
111 | 			UpdatedAt: attrs.Updated,
112 | 		})
113 | 	}
114 | 	return files, nil
115 | }
116 | 
117 | func (s *GCSCache) Exists(key string) bool {
118 | 	obj := s.session.Bucket(s.bucketName).Object(key)
119 | 	_, err := obj.Attrs(s.ctx)
120 | 	return err == nil
121 | }
122 | 
123 | func (s *GCSCache) IsCacheDisabled() bool {
124 | 	return s.noCache
125 | }
126 | 
127 | func (s *GCSCache) GetName() string {
128 | 	return "gcs"
129 | }
130 | 
131 | func (s *GCSCache) DisableCache() {
132 | 	s.noCache = true
133 | }
134 | 


--------------------------------------------------------------------------------
/pkg/cache/interplex_based_test.go:
--------------------------------------------------------------------------------
 1 | package cache
 2 | 
 3 | import (
 4 | 	rpc "buf.build/gen/go/interplex-ai/schemas/grpc/go/protobuf/schema/v1/schemav1grpc"
 5 | 	schemav1 "buf.build/gen/go/interplex-ai/schemas/protocolbuffers/go/protobuf/schema/v1"
 6 | 	"context"
 7 | 	"errors"
 8 | 	"google.golang.org/grpc"
 9 | 	"net"
10 | 	"testing"
11 | )
12 | 
13 | func TestInterplexCache(t *testing.T) {
14 | 	cache := &InterplexCache{
15 | 		configuration: InterplexCacheConfiguration{
16 | 			ConnectionString: "localhost:50051",
17 | 		},
18 | 	}
19 | 
20 | 	// Mock GRPC server setup
21 | 	go func() {
22 | 		lis, err := net.Listen("tcp", ":50051")
23 | 		if err != nil {
24 | 			t.Fatalf("failed to listen: %v", err)
25 | 		}
26 | 		s := grpc.NewServer()
27 | 		rpc.RegisterCacheServiceServer(s, &mockCacheService{})
28 | 		if err := s.Serve(lis); err != nil {
29 | 			t.Fatalf("failed to serve: %v", err)
30 | 		}
31 | 	}()
32 | 
33 | 	t.Run("TestStore", func(t *testing.T) {
34 | 		err := cache.Store("key1", "value1")
35 | 		if err != nil {
36 | 			t.Errorf("Error storing value: %v", err)
37 | 		}
38 | 	})
39 | 
40 | 	t.Run("TestLoad", func(t *testing.T) {
41 | 		value, err := cache.Load("key1")
42 | 		if err != nil {
43 | 			t.Errorf("Error loading value: %v", err)
44 | 		}
45 | 		if value != "value1" {
46 | 			t.Errorf("Expected value1, got %v", value)
47 | 		}
48 | 	})
49 | 
50 | 	t.Run("TestExists", func(t *testing.T) {
51 | 		exists := cache.Exists("key1")
52 | 		if !exists {
53 | 			t.Errorf("Expected key1 to exist")
54 | 		}
55 | 	})
56 | }
57 | 
58 | type mockCacheService struct {
59 | 	rpc.UnimplementedCacheServiceServer
60 | 	data map[string]string
61 | }
62 | 
63 | func (m *mockCacheService) Set(ctx context.Context, req *schemav1.SetRequest) (*schemav1.SetResponse, error) {
64 | 	if m.data == nil {
65 | 		m.data = make(map[string]string)
66 | 	}
67 | 	m.data[req.Key] = req.Value
68 | 	return &schemav1.SetResponse{}, nil
69 | }
70 | 
71 | func (m *mockCacheService) Get(ctx context.Context, req *schemav1.GetRequest) (*schemav1.GetResponse, error) {
72 | 	value, exists := m.data[req.Key]
73 | 	if !exists {
74 | 		return nil, errors.New("key not found")
75 | 	}
76 | 	return &schemav1.GetResponse{Value: value}, nil
77 | }
78 | 


--------------------------------------------------------------------------------
/pkg/cache/types.go:
--------------------------------------------------------------------------------
 1 | package cache
 2 | 
 3 | import "time"
 4 | 
 5 | type CacheProvider struct {
 6 | 	CurrentCacheType string                      `mapstructure:"currentCacheType" yaml:"currentCacheType"`
 7 | 	GCS              GCSCacheConfiguration       `mapstructure:"gcs" yaml:"gcs,omitempty"`
 8 | 	Azure            AzureCacheConfiguration     `mapstructure:"azure" yaml:"azure,omitempty"`
 9 | 	S3               S3CacheConfiguration        `mapstructure:"s3" yaml:"s3,omitempty"`
10 | 	Interplex        InterplexCacheConfiguration `mapstructure:"interplex" yaml:"interplex,omitempty"`
11 | }
12 | 
13 | type CacheObjectDetails struct {
14 | 	Name      string
15 | 	UpdatedAt time.Time
16 | }
17 | 


--------------------------------------------------------------------------------
/pkg/common/types.go:
--------------------------------------------------------------------------------
 1 | /*
 2 | Copyright 2023 The K8sGPT Authors.
 3 | Licensed under the Apache License, Version 2.0 (the "License");
 4 | you may not use this file except in compliance with the License.
 5 | You may obtain a copy of the License at
 6 |     http://www.apache.org/licenses/LICENSE-2.0
 7 | Unless required by applicable law or agreed to in writing, software
 8 | distributed under the License is distributed on an "AS IS" BASIS,
 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 | */
13 | 
14 | package common
15 | 
16 | import (
17 | 	"context"
18 | 	"time"
19 | 
20 | 	openapi_v2 "github.com/google/gnostic/openapiv2"
21 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
22 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
23 | 	keda "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
24 | 	kyverno "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2"
25 | 	regv1 "k8s.io/api/admissionregistration/v1"
26 | 	appsv1 "k8s.io/api/apps/v1"
27 | 	autov2 "k8s.io/api/autoscaling/v2"
28 | 	v1 "k8s.io/api/core/v1"
29 | 	networkv1 "k8s.io/api/networking/v1"
30 | 	policyv1 "k8s.io/api/policy/v1"
31 | 	gtwapi "sigs.k8s.io/gateway-api/apis/v1"
32 | )
33 | 
34 | type IAnalyzer interface {
35 | 	Analyze(analysis Analyzer) ([]Result, error)
36 | }
37 | 
38 | type Analyzer struct {
39 | 	Client        *kubernetes.Client
40 | 	Context       context.Context
41 | 	Namespace     string
42 | 	LabelSelector string
43 | 	AIClient      ai.IAI
44 | 	PreAnalysis   map[string]PreAnalysis
45 | 	Results       []Result
46 | 	OpenapiSchema *openapi_v2.Document
47 | }
48 | 
49 | type PreAnalysis struct {
50 | 	Pod                      v1.Pod
51 | 	FailureDetails           []Failure
52 | 	Deployment               appsv1.Deployment
53 | 	ReplicaSet               appsv1.ReplicaSet
54 | 	PersistentVolumeClaim    v1.PersistentVolumeClaim
55 | 	Endpoint                 v1.Endpoints
56 | 	Ingress                  networkv1.Ingress
57 | 	HorizontalPodAutoscalers autov2.HorizontalPodAutoscaler
58 | 	PodDisruptionBudget      policyv1.PodDisruptionBudget
59 | 	StatefulSet              appsv1.StatefulSet
60 | 	NetworkPolicy            networkv1.NetworkPolicy
61 | 	Node                     v1.Node
62 | 	ValidatingWebhook        regv1.ValidatingWebhookConfiguration
63 | 	MutatingWebhook          regv1.MutatingWebhookConfiguration
64 | 	GatewayClass             gtwapi.GatewayClass
65 | 	Gateway                  gtwapi.Gateway
66 | 	HTTPRoute                gtwapi.HTTPRoute
67 | 	// Integrations
68 | 	ScaledObject               keda.ScaledObject
69 | 	KyvernoPolicyReport        kyverno.PolicyReport
70 | 	KyvernoClusterPolicyReport kyverno.ClusterPolicyReport
71 | }
72 | 
73 | type Result struct {
74 | 	Kind         string    `json:"kind"`
75 | 	Name         string    `json:"name"`
76 | 	Error        []Failure `json:"error"`
77 | 	Details      string    `json:"details"`
78 | 	ParentObject string    `json:"parentObject"`
79 | }
80 | 
81 | type AnalysisStats struct {
82 | 	Analyzer     string        `json:"analyzer"`
83 | 	DurationTime time.Duration `json:"durationTime"`
84 | }
85 | 
86 | type Failure struct {
87 | 	Text          string
88 | 	KubernetesDoc string
89 | 	Sensitive     []Sensitive
90 | }
91 | 
92 | type Sensitive struct {
93 | 	Unmasked string
94 | 	Masked   string
95 | }
96 | 


--------------------------------------------------------------------------------
/pkg/custom/client.go:
--------------------------------------------------------------------------------
 1 | package custom
 2 | 
 3 | import (
 4 | 	rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
 5 | 	schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
 6 | 	"context"
 7 | 	"fmt"
 8 | 	"github.com/k8sgpt-ai/k8sgpt/pkg/common"
 9 | 	"google.golang.org/grpc"
10 | 	"google.golang.org/grpc/credentials/insecure"
11 | )
12 | 
13 | type Client struct {
14 | 	c              *grpc.ClientConn
15 | 	analyzerClient rpc.CustomAnalyzerServiceClient
16 | }
17 | 
18 | func NewClient(c Connection) (*Client, error) {
19 | 
20 | 	//nolint:staticcheck // Ignoring SA1019 for compatibility reasons
21 | 	conn, err := grpc.Dial(fmt.Sprintf("%s:%s", c.Url, c.Port), grpc.WithTransportCredentials(insecure.NewCredentials()))
22 | 
23 | 	if err != nil {
24 | 		return nil, err
25 | 	}
26 | 	client := rpc.NewCustomAnalyzerServiceClient(conn)
27 | 	return &Client{
28 | 		c:              conn,
29 | 		analyzerClient: client,
30 | 	}, nil
31 | }
32 | 
33 | func (cli *Client) Run() (common.Result, error) {
34 | 	var result common.Result
35 | 	req := &schemav1.RunRequest{}
36 | 	res, err := cli.analyzerClient.Run(context.Background(), req)
37 | 	if err != nil {
38 | 		return result, err
39 | 	}
40 | 	if res.Result != nil {
41 | 
42 | 		// We should refactor this, because Error and Failure do not map 1:1 from K8sGPT/schema
43 | 		var errorsFound []common.Failure
44 | 		for _, e := range res.Result.Error {
45 | 			errorsFound = append(errorsFound, common.Failure{
46 | 				Text: e.Text,
47 | 				// TODO: Support sensitive data
48 | 			})
49 | 		}
50 | 
51 | 		result.Name = res.Result.Name
52 | 		result.Kind = res.Result.Kind
53 | 		result.Details = res.Result.Details
54 | 		result.ParentObject = res.Result.ParentObject
55 | 		result.Error = errorsFound
56 | 	}
57 | 	return result, nil
58 | }
59 | 


--------------------------------------------------------------------------------
/pkg/custom/types.go:
--------------------------------------------------------------------------------
 1 | package custom
 2 | 
 3 | type Connection struct {
 4 | 	Url  string `json:"url"`
 5 | 	Port string `json:"port"`
 6 | }
 7 | type CustomAnalyzer struct {
 8 | 	Name       string     `json:"name"`
 9 | 	Connection Connection `json:"connection"`
10 | }
11 | 


--------------------------------------------------------------------------------
/pkg/custom_analyzer/customAnalyzer.go:
--------------------------------------------------------------------------------
 1 | package custom_analyzer
 2 | 
 3 | import (
 4 | 	"fmt"
 5 | 	"reflect"
 6 | 	"regexp"
 7 | )
 8 | 
 9 | type CustomAnalyzerConfiguration struct {
10 | 	Name       string     `mapstructure:"name"`
11 | 	Connection Connection `mapstructure:"connection"`
12 | }
13 | 
14 | type Connection struct {
15 | 	Url  string `mapstructure:"url"`
16 | 	Port int    `mapstructure:"port"`
17 | }
18 | 
19 | type CustomAnalyzer struct{}
20 | 
21 | func NewCustomAnalyzer() *CustomAnalyzer {
22 | 	return &CustomAnalyzer{}
23 | }
24 | 
25 | func (*CustomAnalyzer) Check(actualConfig []CustomAnalyzerConfiguration, name, url string, port int) error {
26 | 	validNameRegex := `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*



    
    

    
    

    
    
    
    

    
    
    
    




    

    
The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
27 | validName := regexp.MustCompile(validNameRegex) 28 | if !validName.MatchString(name) { 29 | return fmt.Errorf("invalid name format. Must match %s", validNameRegex) 30 | } 31 | 32 | for _, analyzer := range actualConfig { 33 | if analyzer.Name == name { 34 | return fmt.Errorf("custom analyzer with the name '%s' already exists. Please use a different name", name) 35 | } 36 | 37 | if reflect.DeepEqual(analyzer.Connection, Connection{ 38 | Url: url, 39 | Port: port, 40 | }) { 41 | return fmt.Errorf("custom analyzer with the same connection configuration (URL: '%s', Port: %d) already exists. Please use a different URL or port", url, port) 42 | } 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/integration/aws/aws.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | type AWS struct { 13 | sess *session.Session 14 | } 15 | 16 | func (a *AWS) Deploy(namespace string) error { 17 | 18 | return nil 19 | } 20 | 21 | func (a *AWS) UnDeploy(namespace string) error { 22 | a.sess = nil 23 | return nil 24 | } 25 | 26 | func (a *AWS) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) { 27 | // Retrieve AWS credentials from the environment 28 | accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID") 29 | secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY") 30 | awsProfile := os.Getenv("AWS_PROFILE") 31 | 32 | var sess *session.Session 33 | if accessKeyID != "" && secretAccessKey != "" { 34 | // Use access keys if both are provided 35 | sess = session.Must(session.NewSessionWithOptions(session.Options{ 36 | Config: aws.Config{}, 37 | })) 38 | } else { 39 | // Use AWS profile, default to "default" if not set 40 | if awsProfile == "" { 41 | awsProfile = "default" 42 | } 43 | sess = session.Must(session.NewSessionWithOptions(session.Options{ 44 | Profile: awsProfile, 45 | SharedConfigState: session.SharedConfigEnable, 46 | })) 47 | } 48 | 49 | a.sess = sess 50 | (*mergedMap)["EKS"] = &EKSAnalyzer{ 51 | session: a.sess, 52 | } 53 | } 54 | 55 | func (a *AWS) GetAnalyzerName() []string { 56 | 57 | return []string{"EKS"} 58 | } 59 | 60 | func (a *AWS) GetNamespace() (string, error) { 61 | 62 | return "", nil 63 | } 64 | 65 | func (a *AWS) OwnsAnalyzer(s string) bool { 66 | for _, az := range a.GetAnalyzerName() { 67 | if s == az { 68 | return true 69 | } 70 | } 71 | return false 72 | } 73 | 74 | func (a *AWS) isFilterActive() bool { 75 | activeFilters := viper.GetStringSlice("active_filters") 76 | 77 | for _, filter := range a.GetAnalyzerName() { 78 | for _, af := range activeFilters { 79 | if af == filter { 80 | return true 81 | } 82 | } 83 | } 84 | 85 | return false 86 | } 87 | 88 | func (a *AWS) IsActivate() bool { 89 | if a.isFilterActive() { 90 | return true 91 | } else { 92 | return false 93 | } 94 | } 95 | 96 | func NewAWS() *AWS { 97 | return &AWS{} 98 | } 99 | -------------------------------------------------------------------------------- /pkg/integration/aws/eks.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "errors" 5 | "github.com/spf13/viper" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/eks" 12 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 13 | "k8s.io/client-go/tools/clientcmd" 14 | ) 15 | 16 | type EKSAnalyzer struct { 17 | session *session.Session 18 | } 19 | 20 | func (e *EKSAnalyzer) Analyze(analysis common.Analyzer) ([]common.Result, error) { 21 | var cr []common.Result = []common.Result{} 22 | _ = map[string]common.PreAnalysis{} 23 | svc := eks.New(e.session) 24 | // Get the name of the current cluster 25 | var kubeconfig string 26 | kubeconfigFromPath := viper.GetString("kubeconfig") 27 | if kubeconfigFromPath != "" { 28 | kubeconfig = kubeconfigFromPath 29 | } else { 30 | kubeconfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") 31 | } 32 | config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 33 | &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, 34 | &clientcmd.ConfigOverrides{ 35 | CurrentContext: "", 36 | }).RawConfig() 37 | if err != nil { 38 | return cr, err 39 | } 40 | currentConfig := config.CurrentContext 41 | 42 | if !strings.Contains(currentConfig, "eks") { 43 | return cr, errors.New("EKS cluster was not detected") 44 | } 45 | 46 | input := &eks.ListClustersInput{} 47 | result, err := svc.ListClusters(input) 48 | if err != nil { 49 | return cr, err 50 | } 51 | for _, cluster := range result.Clusters { 52 | // describe the cluster 53 | if !strings.Contains(currentConfig, *cluster) { 54 | continue 55 | } 56 | input := &eks.DescribeClusterInput{ 57 | Name: cluster, 58 | } 59 | result, err := svc.DescribeCluster(input) 60 | if err != nil { 61 | return cr, err 62 | } 63 | if len(result.Cluster.Health.Issues) > 0 { 64 | for _, issue := range result.Cluster.Health.Issues { 65 | err := make([]common.Failure, 0) 66 | err = append(err, common.Failure{ 67 | Text: issue.String(), 68 | KubernetesDoc: "", 69 | Sensitive: nil, 70 | }) 71 | cr = append(cr, common.Result{ 72 | Kind: "EKS", 73 | Name: "AWS/EKS", 74 | Error: err, 75 | }) 76 | } 77 | } 78 | } 79 | return cr, nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/integration/kyverno/analyzer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package kyverno 15 | 16 | import ( 17 | "context" 18 | "testing" 19 | 20 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 21 | "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" 22 | "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2" 23 | 24 | "github.com/stretchr/testify/assert" 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 29 | ) 30 | 31 | func buildFakeClient(t *testing.T) client.Client { 32 | objects := []client.Object{ 33 | &v1alpha2.PolicyReport{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | Name: "policy-1", 36 | Namespace: "test-ns", 37 | }, 38 | Results: []v1alpha2.PolicyReportResult{ 39 | { 40 | Category: "Other", 41 | Message: "validation failure: Images built more than 6 months ago are prohibited.", 42 | Policy: "block-stale-images", 43 | Result: "fail", 44 | }, 45 | }, 46 | }, 47 | &v1alpha2.PolicyReport{ 48 | ObjectMeta: metav1.ObjectMeta{ 49 | Name: "policy-2", 50 | Namespace: "other-ns", 51 | }, 52 | Results: []v1alpha2.PolicyReportResult{ 53 | { 54 | Category: "Other", 55 | Message: "validation failure: Images built more than 6 months ago are prohibited.", 56 | Policy: "block-stale-images", 57 | Result: "fail", 58 | }, 59 | }, 60 | }, 61 | } 62 | 63 | scheme := runtime.NewScheme() 64 | err := v1alpha2.AddToScheme(scheme) 65 | if err != nil { 66 | t.Error(err) 67 | } 68 | return fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() 69 | } 70 | 71 | func TestAnalyzerNamespaceFiltering(t *testing.T) { 72 | 73 | config := common.Analyzer{ 74 | Client: &kubernetes.Client{ 75 | CtrlClient: buildFakeClient(t), 76 | }, 77 | Context: context.Background(), 78 | Namespace: "test-ns", 79 | } 80 | 81 | // Create and run analyzer 82 | analyzer := KyvernoAnalyzer{ 83 | policyReportAnalysis: true, 84 | } 85 | results, err := analyzer.Analyze(config) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | 90 | // Verify results 91 | assert.Equal(t, len(results), 1) 92 | assert.Equal(t, results[0].Kind, "PolicyReport") 93 | assert.Equal(t, results[0].Name, "test-ns/policy-1") 94 | } 95 | 96 | func TestAnalyzerAllNamespace(t *testing.T) { 97 | 98 | config := common.Analyzer{ 99 | Client: &kubernetes.Client{ 100 | CtrlClient: buildFakeClient(t), 101 | }, 102 | Context: context.Background(), 103 | } 104 | 105 | // Create and run analyzer 106 | analyzer := KyvernoAnalyzer{ 107 | policyReportAnalysis: true, 108 | } 109 | results, err := analyzer.Analyze(config) 110 | if err != nil { 111 | t.Error(err) 112 | } 113 | 114 | // Verify results 115 | assert.Equal(t, len(results), 2) 116 | 117 | } 118 | -------------------------------------------------------------------------------- /pkg/integration/kyverno/kyverno.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package kyverno 15 | 16 | import ( 17 | "os" 18 | 19 | "github.com/fatih/color" 20 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 21 | "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" 22 | "github.com/spf13/viper" 23 | ) 24 | 25 | type Kyverno struct{} 26 | 27 | func NewKyverno() *Kyverno { 28 | return &Kyverno{} 29 | } 30 | 31 | func (k *Kyverno) GetAnalyzerName() []string { 32 | return []string{ 33 | //from wgpolicyk8s.io/v1alpha2 34 | "PolicyReport", 35 | "ClusterPolicyReport", 36 | } 37 | } 38 | 39 | func (k *Kyverno) OwnsAnalyzer(analyzer string) bool { 40 | 41 | for _, a := range k.GetAnalyzerName() { 42 | if analyzer == a { 43 | return true 44 | } 45 | } 46 | return false 47 | } 48 | 49 | func (k *Kyverno) isDeployed() bool { 50 | // check if wgpolicyk8s apigroup is available as a marker if new policy resource available is installed on the cluster 51 | kubecontext := viper.GetString("kubecontext") 52 | kubeconfig := viper.GetString("kubeconfig") 53 | client, err := kubernetes.NewClient(kubecontext, kubeconfig) 54 | if err != nil { 55 | // TODO: better error handling 56 | color.Red("Error initialising kubernetes client: %v", err) 57 | os.Exit(1) 58 | } 59 | groups, _, err := client.Client.Discovery().ServerGroupsAndResources() 60 | if err != nil { 61 | // TODO: better error handling 62 | color.Red("Error initialising discovery client: %v", err) 63 | os.Exit(1) 64 | } 65 | 66 | for _, group := range groups { 67 | if group.Name == "kyverno.io" { 68 | return true 69 | } 70 | } 71 | 72 | return false 73 | } 74 | 75 | func (k *Kyverno) isFilterActive() bool { 76 | activeFilters := viper.GetStringSlice("active_filters") 77 | 78 | for _, filter := range k.GetAnalyzerName() { 79 | for _, af := range activeFilters { 80 | if af == filter { 81 | return true 82 | } 83 | } 84 | } 85 | 86 | return false 87 | } 88 | 89 | func (k *Kyverno) IsActivate() bool { 90 | if k.isFilterActive() && k.isDeployed() { 91 | return true 92 | } else { 93 | return false 94 | } 95 | } 96 | 97 | func (k *Kyverno) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) { 98 | 99 | (*mergedMap)["PolicyReport"] = &KyvernoAnalyzer{ 100 | policyReportAnalysis: true, 101 | } 102 | (*mergedMap)["ClusterPolicyReport"] = &KyvernoAnalyzer{ 103 | clusterReportAnalysis: true, 104 | } 105 | } 106 | 107 | func (k *Kyverno) Deploy(namespace string) error { 108 | return nil 109 | } 110 | 111 | func (k *Kyverno) UnDeploy(_ string) error { 112 | return nil 113 | } 114 | 115 | func (t *Kyverno) GetNamespace() (string, error) { 116 | return "", nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/integration/prometheus/prometheus.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/fatih/color" 10 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 11 | "github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes" 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | const ( 16 | ConfigValidate = "PrometheusConfigValidate" 17 | ConfigRelabel = "PrometheusConfigRelabelReport" 18 | ) 19 | 20 | type Prometheus struct { 21 | } 22 | 23 | func NewPrometheus() *Prometheus { 24 | return &Prometheus{} 25 | } 26 | 27 | func (p *Prometheus) Deploy(namespace string) error { 28 | // no-op 29 | color.Green("Activating prometheus integration...") 30 | // TODO(pintohutch): add timeout or inherit an upstream context 31 | // for better signal management. 32 | ctx := context.Background() 33 | kubecontext := viper.GetString("kubecontext") 34 | kubeconfig := viper.GetString("kubeconfig") 35 | client, err := kubernetes.NewClient(kubecontext, kubeconfig) 36 | if err != nil { 37 | color.Red("Error initialising kubernetes client: %v", err) 38 | os.Exit(1) 39 | } 40 | 41 | // We just care about existing deployments. 42 | // Try and find Prometheus configurations in the cluster using the provided namespace. 43 | // 44 | // Note: We could cache this state and inject it into the various analyzers 45 | // to save additional parsing later. 46 | // However, the state of the cluster can change from activation to analysis, 47 | // so we would want to run this again on each analyze call anyway. 48 | // 49 | // One consequence of this is one can run `activate` in one namespace 50 | // and run `analyze` in another, without issues, as long as Prometheus 51 | // is found in both. 52 | // We accept this as a trade-off for the time-being to avoid having the tool 53 | // manage Prometheus on the behalf of users. 54 | podConfigs, err := findPrometheusPodConfigs(ctx, client.GetClient(), namespace) 55 | if err != nil { 56 | color.Red("Error discovering Prometheus workloads: %v", err) 57 | os.Exit(1) 58 | } 59 | if len(podConfigs) == 0 { 60 | color.Yellow(fmt.Sprintf(`Prometheus installation not found in namespace: %s. 61 | Please ensure Prometheus is deployed to analyze.`, namespace)) 62 | return errors.New("no prometheus installation found") 63 | } 64 | // Prime state of the analyzer so 65 | color.Green("Found existing installation") 66 | return nil 67 | } 68 | 69 | func (p *Prometheus) UnDeploy(_ string) error { 70 | // no-op 71 | // We just care about existing deployments. 72 | color.Yellow("Integration will leave Prometheus resources deployed. This is an effective no-op in the cluster.") 73 | return nil 74 | } 75 | 76 | func (p *Prometheus) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) { 77 | (*mergedMap)[ConfigValidate] = &ConfigAnalyzer{} 78 | (*mergedMap)[ConfigRelabel] = &RelabelAnalyzer{} 79 | } 80 | 81 | func (p *Prometheus) GetAnalyzerName() []string { 82 | return []string{ConfigValidate, ConfigRelabel} 83 | } 84 | 85 | func (p *Prometheus) GetNamespace() (string, error) { 86 | return "", nil 87 | } 88 | 89 | func (p *Prometheus) OwnsAnalyzer(analyzer string) bool { 90 | return (analyzer == ConfigValidate) || (analyzer == ConfigRelabel) 91 | } 92 | 93 | func (t *Prometheus) IsActivate() bool { 94 | activeFilters := viper.GetStringSlice("active_filters") 95 | 96 | for _, filter := range t.GetAnalyzerName() { 97 | for _, af := range activeFilters { 98 | if af == filter { 99 | return true 100 | } 101 | } 102 | } 103 | 104 | return false 105 | } 106 | -------------------------------------------------------------------------------- /pkg/integration/prometheus/relabel_analyzer.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/k8sgpt-ai/k8sgpt/pkg/common" 7 | "github.com/k8sgpt-ai/k8sgpt/pkg/util" 8 | discoverykube "github.com/prometheus/prometheus/discovery/kubernetes" 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | type RelabelAnalyzer struct { 13 | } 14 | 15 | func (r *RelabelAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) { 16 | ctx := a.Context 17 | client := a.Client.GetClient() 18 | namespace := a.Namespace 19 | kind := ConfigRelabel 20 | 21 | podConfigs, err := findPrometheusPodConfigs(ctx, client, namespace) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | var preAnalysis = map[string]common.PreAnalysis{} 27 | for _, pc := range podConfigs { 28 | var failures []common.Failure 29 | pod := pc.pod 30 | 31 | // Check upstream validation. 32 | // The Prometheus configuration structs do not generally have validation 33 | // methods and embed their validation logic in the UnmarshalYAML methods. 34 | config, _ := unmarshalPromConfigBytes(pc.b) 35 | // Limit output for brevity. 36 | limit := 6 37 | i := 0 38 | for _, sc := range config.ScrapeConfigs { 39 | if i == limit { 40 | break 41 | } 42 | if sc == nil { 43 | continue 44 | } 45 | brc, _ := yaml.Marshal(sc.RelabelConfigs) 46 | var bsd []byte 47 | for _, cfg := range sc.ServiceDiscoveryConfigs { 48 | ks, ok := cfg.(*discoverykube.SDConfig) 49 | if !ok { 50 | continue 51 | } 52 | bsd, _ = yaml.Marshal(ks) 53 | } 54 | // Don't bother with relabel analysis if the scrape config 55 | // or service discovery config are empty. 56 | if len(brc) == 0 || len(bsd) == 0 { 57 | continue 58 | } 59 | failures = append(failures, common.Failure{ 60 | Text: fmt.Sprintf("job_name:\n%s\nrelabel_configs:\n%s\nkubernetes_sd_configs:\n%s\n", sc.JobName, string(brc), string(bsd)), 61 | }) 62 | i++ 63 | } 64 | 65 | if len(failures) > 0 { 66 | preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{ 67 | Pod: *pod, 68 | FailureDetails: failures, 69 | } 70 | } 71 | } 72 | 73 | for key, value := range preAnalysis { 74 | var currentAnalysis = common.Result{ 75 | Kind: kind, 76 | Name: key, 77 | Error: value.FailureDetails, 78 | } 79 | parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta) 80 | currentAnalysis.ParentObject = parent 81 | a.Results = append(a.Results, currentAnalysis) 82 | } 83 | 84 | return a.Results, nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/kubernetes/apireference.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | openapi_v2 "github.com/google/gnostic/openapiv2" 8 | ) 9 | 10 | func (k *K8sApiReference) GetApiDocV2(field string) string { 11 | startPoint := "" 12 | // the path must be formated like "path1.path2.path3" 13 | paths := strings.Split(field, ".") 14 | group := strings.Split(k.ApiVersion.Group, ".") 15 | definitions := k.OpenapiSchema.GetDefinitions().GetAdditionalProperties() 16 | 17 | // extract the startpoint by searching the highest leaf corresponding to the requested group qnd kind 18 | for _, prop := range definitions { 19 | if strings.HasSuffix(prop.GetName(), fmt.Sprintf("%s.%s.%s", group[0], k.ApiVersion.Version, k.Kind)) { 20 | startPoint = prop.GetName() 21 | 22 | break 23 | } 24 | } 25 | 26 | // recursively parse the definitions to find the description of the latest part of the given path 27 | description := k.recursePath(definitions, startPoint, paths) 28 | 29 | return description 30 | } 31 | 32 | func (k *K8sApiReference) recursePath(definitions []*openapi_v2.NamedSchema, leaf string, paths []string) string { 33 | description := "" 34 | 35 | for _, prop := range definitions { 36 | // search the requested leaf 37 | if prop.GetName() == leaf { 38 | for _, addProp := range prop.GetValue().GetProperties().GetAdditionalProperties() { 39 | // search the additional property of the leaf corresponding the current path 40 | if addProp.GetName() == paths[0] { 41 | // the last path or the path is string, we get the description and we go out 42 | if len(paths) == 1 || addProp.GetValue().GetType().String() == "value:\"string\"" { 43 | // extract the path description as we are at the end of the paths 44 | description = addProp.GetValue().Description 45 | } else { 46 | // the path is an object, we extract the xref 47 | if addProp.GetValue().GetXRef() != "" { 48 | splitRef := strings.Split(addProp.GetValue().GetXRef(), "/") 49 | reducedPaths := paths[1:] 50 | description = k.recursePath(definitions, splitRef[len(splitRef)-1], reducedPaths) 51 | } 52 | 53 | // the path is an array, we take the first xref from the items 54 | if len(addProp.GetValue().GetItems().GetSchema()) == 1 { 55 | splitRef := strings.Split(addProp.GetValue().GetItems().GetSchema()[0].GetXRef(), "/") 56 | reducedPaths := paths[1:] 57 | description = k.recursePath(definitions, splitRef[len(splitRef)-1], reducedPaths) 58 | } 59 | } 60 | 61 | break 62 | } 63 | } 64 | 65 | break 66 | } 67 | } 68 | 69 | return description 70 | } 71 | -------------------------------------------------------------------------------- /pkg/kubernetes/apireference_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package kubernetes 15 | 16 | import ( 17 | "testing" 18 | 19 | openapi_v2 "github.com/google/gnostic/openapiv2" 20 | "github.com/stretchr/testify/require" 21 | "k8s.io/apimachinery/pkg/runtime/schema" 22 | ) 23 | 24 | func TestGetApiDocV2(t *testing.T) { 25 | k8s := &K8sApiReference{ 26 | ApiVersion: schema.GroupVersion{ 27 | Group: "group.v1", 28 | Version: "v1", 29 | }, 30 | OpenapiSchema: &openapi_v2.Document{ 31 | Definitions: &openapi_v2.Definitions{ 32 | AdditionalProperties: []*openapi_v2.NamedSchema{ 33 | { 34 | Name: "group.v1.kind", 35 | Value: &openapi_v2.Schema{ 36 | Title: "test", 37 | Properties: &openapi_v2.Properties{ 38 | AdditionalProperties: []*openapi_v2.NamedSchema{ 39 | { 40 | Name: "schema1", 41 | Value: &openapi_v2.Schema{ 42 | Title: "test", 43 | Description: "schema1 description", 44 | Type: &openapi_v2.TypeItem{ 45 | Value: []string{"string"}, 46 | }, 47 | }, 48 | }, 49 | { 50 | Name: "schema2", 51 | Value: &openapi_v2.Schema{ 52 | Items: &openapi_v2.ItemsItem{ 53 | Schema: []*openapi_v2.Schema{ 54 | { 55 | Title: "random-schema", 56 | }, 57 | }, 58 | }, 59 | Title: "test", 60 | XRef: "xref", 61 | Description: "schema2 description", 62 | Type: &openapi_v2.TypeItem{ 63 | Value: []string{"bool"}, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }, 70 | }, 71 | { 72 | Name: "group", 73 | }, 74 | }, 75 | }, 76 | }, 77 | Kind: "kind", 78 | } 79 | 80 | tests := []struct { 81 | name string 82 | field string 83 | expectedOutput string 84 | }{ 85 | { 86 | name: "empty field", 87 | }, 88 | { 89 | name: "2 schemas", 90 | field: "schema2.schema1", 91 | expectedOutput: "", 92 | }, 93 | { 94 | name: "schema1 description", 95 | field: "schema1", 96 | expectedOutput: "schema1 description", 97 | }, 98 | } 99 | 100 | for _, tt := range tests { 101 | t.Run(tt.name, func(t *testing.T) { 102 | output := k8s.GetApiDocV2(tt.field) 103 | require.Equal(t, tt.expectedOutput, output) 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pkg/kubernetes/kubernetes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package kubernetes 15 | 16 | import ( 17 | "k8s.io/client-go/kubernetes" 18 | _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" 19 | "k8s.io/client-go/rest" 20 | "k8s.io/client-go/tools/clientcmd" 21 | ctrl "sigs.k8s.io/controller-runtime/pkg/client" 22 | ) 23 | 24 | func (c *Client) GetConfig() *rest.Config { 25 | return c.Config 26 | } 27 | 28 | func (c *Client) GetClient() kubernetes.Interface { 29 | return c.Client 30 | } 31 | 32 | func (c *Client) GetCtrlClient() ctrl.Client { 33 | return c.CtrlClient 34 | } 35 | 36 | func NewClient(kubecontext string, kubeconfig string) (*Client, error) { 37 | var config *rest.Config 38 | config, err := rest.InClusterConfig() 39 | if kubeconfig != "" || err != nil { 40 | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 41 | 42 | if kubeconfig != "" { 43 | loadingRules.ExplicitPath = kubeconfig 44 | } 45 | 46 | clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 47 | loadingRules, 48 | &clientcmd.ConfigOverrides{ 49 | CurrentContext: kubecontext, 50 | }) 51 | // create the clientset 52 | config, err = clientConfig.ClientConfig() 53 | if err != nil { 54 | return nil, err 55 | } 56 | } 57 | clientSet, err := kubernetes.NewForConfig(config) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | ctrlClient, err := ctrl.New(config, ctrl.Options{}) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | serverVersion, err := clientSet.ServerVersion() 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return &Client{ 73 | Client: clientSet, 74 | CtrlClient: ctrlClient, 75 | Config: config, 76 | ServerVersion: serverVersion, 77 | }, nil 78 | } 79 | -------------------------------------------------------------------------------- /pkg/kubernetes/types.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | openapi_v2 "github.com/google/gnostic/openapiv2" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | "k8s.io/apimachinery/pkg/version" 7 | "k8s.io/client-go/kubernetes" 8 | "k8s.io/client-go/rest" 9 | ctrl "sigs.k8s.io/controller-runtime/pkg/client" 10 | ) 11 | 12 | type Client struct { 13 | Client kubernetes.Interface 14 | CtrlClient ctrl.Client 15 | Config *rest.Config 16 | ServerVersion *version.Info 17 | } 18 | 19 | type K8sApiReference struct { 20 | ApiVersion schema.GroupVersion 21 | Kind string 22 | OpenapiSchema *openapi_v2.Document 23 | } 24 | -------------------------------------------------------------------------------- /pkg/server/README.md: -------------------------------------------------------------------------------- 1 | # K8sGPT MCP Server 2 | 3 | This 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. 4 | 5 | ## Components 6 | 7 | - `mcp.go`: The main MCP server implementation 8 | - `server.go`: The HTTP server implementation 9 | - `tools.go`: Tool definitions for the MCP server 10 | 11 | ## Features 12 | 13 | The MCP server provides the following features: 14 | 15 | 1. **Analyze Kubernetes Resources**: Analyze Kubernetes resources in a cluster 16 | 2. **Get Cluster Information**: Retrieve information about the Kubernetes cluster 17 | 18 | ## Usage 19 | 20 | To use the MCP server, you need to: 21 | 22 | 1. Initialize the MCP server with a Kubernetes client 23 | 2. Start the server 24 | 3. Connect to the server using an MCP client 25 | 26 | Example: 27 | 28 | ```go 29 | client, err := kubernetes.NewForConfig(config) 30 | if err != nil { 31 | log.Fatalf("Failed to create Kubernetes client: %v", err) 32 | } 33 | 34 | mcpServer := server.NewMCPServer(client) 35 | if err := mcpServer.Start(); err != nil { 36 | log.Fatalf("Failed to start MCP server: %v", err) 37 | } 38 | ``` 39 | 40 | ## Integration 41 | 42 | The MCP server can be integrated with other tools that support the MCP protocol, such as: 43 | 44 | - Mission Control 45 | - Other MCP-compatible tools 46 | 47 | ## License 48 | 49 | This code is licensed under the Apache License 2.0. 50 | -------------------------------------------------------------------------------- /pkg/server/analyze/analyze.go: -------------------------------------------------------------------------------- 1 | package analyze 2 | 3 | import ( 4 | "context" 5 | json "encoding/json" 6 | 7 | schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" 8 | "github.com/k8sgpt-ai/k8sgpt/pkg/analysis" 9 | ) 10 | 11 | func (h *Handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) ( 12 | *schemav1.AnalyzeResponse, 13 | error, 14 | ) { 15 | if i.Output == "" { 16 | i.Output = "json" 17 | } 18 | 19 | if int(i.MaxConcurrency) == 0 { 20 | i.MaxConcurrency = 10 21 | } 22 | 23 | config, err := analysis.NewAnalysis( 24 | i.Backend, 25 | i.Language, 26 | i.Filters, 27 | i.Namespace, 28 | i.LabelSelector, 29 | i.Nocache, 30 | i.Explain, 31 | int(i.MaxConcurrency), 32 | false, // Kubernetes Doc disabled in server mode 33 | false, // Interactive mode disabled in server mode 34 | []string{}, //TODO: add custom http headers in server mode 35 | false, // with stats disable 36 | ) 37 | if err != nil { 38 | return &schemav1.AnalyzeResponse{}, err 39 | } 40 | config.Context = ctx // Replace context for correct timeouts. 41 | defer config.Close() 42 | 43 | if config.CustomAnalyzersAreAvailable() { 44 | config.RunCustomAnalysis() 45 | } 46 | config.RunAnalysis() 47 | 48 | if i.Explain { 49 | err := config.GetAIResults(i.Output, i.Anonymize) 50 | if err != nil { 51 | return &schemav1.AnalyzeResponse{}, err 52 | } 53 | } 54 | 55 | out, err := config.PrintOutput(i.Output) 56 | if err != nil { 57 | return &schemav1.AnalyzeResponse{}, err 58 | } 59 | var obj schemav1.AnalyzeResponse 60 | 61 | err = json.Unmarshal(out, &obj) 62 | if err != nil { 63 | return &schemav1.AnalyzeResponse{}, err 64 | } 65 | 66 | return &obj, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/server/analyze/handler.go: -------------------------------------------------------------------------------- 1 | package analyze 2 | 3 | import rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" 4 | 5 | type Handler struct { 6 | rpc.UnimplementedServerAnalyzerServiceServer 7 | } 8 | -------------------------------------------------------------------------------- /pkg/server/client_example/README.md: -------------------------------------------------------------------------------- 1 | # K8sGPT MCP Client Example 2 | 3 | This directory contains an example of how to use the K8sGPT MCP client in a real-world scenario. 4 | 5 | ## Prerequisites 6 | 7 | - Go 1.16 or later 8 | - Access to a Kubernetes cluster 9 | - `kubectl` configured to access your cluster 10 | 11 | ## Building the Example 12 | 13 | To build the example, run: 14 | 15 | ```bash 16 | go build -o mcp-client-example 17 | ``` 18 | 19 | ## Running the Example 20 | 21 | To run the example, use the following command: 22 | 23 | ```bash 24 | ./mcp-client-example --kubeconfig=/path/to/kubeconfig --namespace=default 25 | ``` 26 | 27 | ### Command-line Flags 28 | 29 | - `--kubeconfig`: Path to the kubeconfig file (optional, defaults to the standard location) 30 | - `--namespace`: Kubernetes namespace to analyze (optional) 31 | 32 | ## Example Output 33 | 34 | When you run the example, you should see output similar to the following: 35 | 36 | ``` 37 | Starting MCP client... 38 | ``` 39 | 40 | The client will continue running until you press Ctrl+C to stop it. 41 | 42 | ## Integration with Mission Control 43 | 44 | To integrate this example with Mission Control, you need to: 45 | 46 | 1. Start the MCP client using the example 47 | 2. Configure Mission Control to connect to the MCP client 48 | 3. Use Mission Control to analyze your Kubernetes cluster 49 | 50 | ## Troubleshooting 51 | 52 | If you encounter any issues, check the following: 53 | 54 | 1. Ensure that your Kubernetes cluster is accessible 55 | 2. Verify that your kubeconfig file is valid 56 | 3. Check that the namespace you specified exists 57 | 58 | ## License 59 | 60 | This code is licensed under the Apache License 2.0. -------------------------------------------------------------------------------- /pkg/server/client_example/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package main 15 | 16 | import ( 17 | "bytes" 18 | "encoding/json" 19 | "flag" 20 | "fmt" 21 | "io" 22 | "log" 23 | "net/http" 24 | "time" 25 | ) 26 | 27 | // AnalyzeRequest represents the input parameters for the analyze tool 28 | type AnalyzeRequest struct { 29 | Namespace string `json:"namespace,omitempty"` 30 | Backend string `json:"backend,omitempty"` 31 | Language string `json:"language,omitempty"` 32 | Filters []string `json:"filters,omitempty"` 33 | LabelSelector string `json:"labelSelector,omitempty"` 34 | NoCache bool `json:"noCache,omitempty"` 35 | Explain bool `json:"explain,omitempty"` 36 | MaxConcurrency int `json:"maxConcurrency,omitempty"` 37 | WithDoc bool `json:"withDoc,omitempty"` 38 | InteractiveMode bool `json:"interactiveMode,omitempty"` 39 | CustomHeaders []string `json:"customHeaders,omitempty"` 40 | WithStats bool `json:"withStats,omitempty"` 41 | } 42 | 43 | // AnalyzeResponse represents the output of the analyze tool 44 | type AnalyzeResponse struct { 45 | Content []struct { 46 | Text string `json:"text"` 47 | Type string `json:"type"` 48 | } `json:"content"` 49 | } 50 | 51 | func main() { 52 | // Parse command line flags 53 | serverPort := flag.String("port", "8089", "Port of the MCP server") 54 | namespace := flag.String("namespace", "", "Kubernetes namespace to analyze") 55 | backend := flag.String("backend", "", "AI backend to use") 56 | language := flag.String("language", "english", "Language for analysis") 57 | flag.Parse() 58 | 59 | // Create analyze request 60 | req := AnalyzeRequest{ 61 | Namespace: *namespace, 62 | Backend: *backend, 63 | Language: *language, 64 | Explain: true, 65 | MaxConcurrency: 10, 66 | } 67 | 68 | // Convert request to JSON 69 | reqJSON, err := json.Marshal(req) 70 | if err != nil { 71 | log.Fatalf("Failed to marshal request: %v", err) 72 | } 73 | 74 | // Create HTTP client with timeout 75 | client := &http.Client{ 76 | Timeout: 5 * time.Minute, 77 | } 78 | 79 | // Send request to MCP server 80 | resp, err := client.Post( 81 | fmt.Sprintf("http://localhost:%s/mcp/analyze", *serverPort), 82 | "application/json", 83 | bytes.NewBuffer(reqJSON), 84 | ) 85 | if err != nil { 86 | log.Fatalf("Failed to send request: %v", err) 87 | } 88 | defer func() { 89 | if err := resp.Body.Close(); err != nil { 90 | log.Printf("Error closing response body: %v", err) 91 | } 92 | }() 93 | 94 | // Read and print raw response for debugging 95 | body, err := io.ReadAll(resp.Body) 96 | if err != nil { 97 | log.Fatalf("Failed to read response body: %v", err) 98 | } 99 | fmt.Printf("Raw response: %s\n", string(body)) 100 | 101 | // Parse response 102 | var analyzeResp AnalyzeResponse 103 | if err := json.Unmarshal(body, &analyzeResp); err != nil { 104 | log.Fatalf("Failed to decode response: %v", err) 105 | } 106 | 107 | // Print results 108 | fmt.Println("Analysis Results:") 109 | if len(analyzeResp.Content) > 0 { 110 | fmt.Println(analyzeResp.Content[0].Text) 111 | } else { 112 | fmt.Println("No results returned") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pkg/server/config/handler.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" 5 | schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" 6 | "context" 7 | ) 8 | 9 | type Handler struct { 10 | rpc.UnimplementedServerConfigServiceServer 11 | } 12 | 13 | func (h *Handler) Shutdown(ctx context.Context, request *schemav1.ShutdownRequest) (*schemav1.ShutdownResponse, error) { 14 | //TODO implement me 15 | panic("implement me") 16 | } 17 | -------------------------------------------------------------------------------- /pkg/server/example/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The K8sGPT Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package main 15 | 16 | import ( 17 | "flag" 18 | "log" 19 | "os" 20 | "os/signal" 21 | "syscall" 22 | 23 | "github.com/k8sgpt-ai/k8sgpt/pkg/ai" 24 | "github.com/k8sgpt-ai/k8sgpt/pkg/server" 25 | "go.uber.org/zap" 26 | ) 27 | 28 | func main() { 29 | // Parse command line flags 30 | port := flag.String("port", "8089", "Port to run the MCP server on") 31 | useHTTP := flag.Bool("http", false, "Enable HTTP mode for MCP server") 32 | flag.Parse() 33 | 34 | // Initialize zap logger 35 | logger, err := zap.NewProduction() 36 | if err != nil { 37 | log.Fatalf("Error creating logger: %v", err) 38 | } 39 | defer func() { 40 | if err := logger.Sync(); err != nil { 41 | log.Printf("Error syncing logger: %v", err) 42 | } 43 | }() 44 | 45 | // Create AI provider 46 | aiProvider := &ai.AIProvider{ 47 | Name: "openai", 48 | Password: os.Getenv("OPENAI_API_KEY"), 49 | Model: "gpt-3.5-turbo", 50 | } 51 | 52 | // Create and start MCP server 53 | mcpServer, err := server.NewMCPServer(*port, aiProvider, *useHTTP, logger) 54 | if err != nil { 55 | log.Fatalf("Error creating MCP server: %v", err) 56 | } 57 | 58 | // Start the server in a goroutine 59 | go func() { 60 | if err := mcpServer.Start(); err != nil { 61 | log.Fatalf("Error starting MCP server: %v", err) 62 | } 63 | }() 64 | 65 | // Handle graceful shutdown 66 | sigChan := make(chan os.Signal, 1) 67 | signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 68 | <-sigChan 69 | 70 | // Cleanup 71 | if err := mcpServer.Close(); err != nil { 72 | log.Printf("Error closing MCP server: %v", err) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pkg/server/log.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "go.uber.org/zap" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/peer" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | func LogInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor { 15 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 16 | start := time.Now() 17 | 18 | // Call the handler to execute the gRPC request 19 | response, err := handler(ctx, req) 20 | 21 | duration := time.Since(start).Milliseconds() 22 | fields := []zap.Field{ 23 | zap.Int64("duration_ms", duration), 24 | zap.String("method", info.FullMethod), 25 | zap.Any("request", req), 26 | } 27 | // Get the remote address from the context 28 | peer, ok := peer.FromContext(ctx) 29 | if ok { 30 | fields = append(fields, zap.String("remote_addr", peer.Addr.String())) 31 | } 32 | 33 | if err != nil { 34 | fields = append(fields, zap.Int32("status_code", int32(status.Code(err)))) 35 | } 36 | message := "request completed" 37 | if err != nil { 38 | message = fmt.Sprintf("request failed. %s", err.Error()) 39 | } 40 | logRequest(logger, fields, int(status.Code(err)), message) 41 | 42 | return response, err 43 | } 44 | } 45 | 46 | func logRequest(logger *zap.Logger, fields []zap.Field, statusCode int, message string) { 47 | if statusCode >= 400 { 48 | logger.Error(message, fields...) 49 | } else { 50 | logger.Info(message, fields...) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/server/query/handler.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc" 4 | 5 | type Handler struct { 6 | rpc.UnimplementedServerQueryServiceServer 7 | } 8 | -------------------------------------------------------------------------------- /pkg/server/query/query.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1" 8 | "github.com/k8sgpt-ai/k8sgpt/pkg/ai" 9 | ) 10 | 11 | func (h *Handler) Query(ctx context.Context, i *schemav1.QueryRequest) ( 12 | *schemav1.QueryResponse, 13 | error, 14 | ) { 15 | // Create client factory and config provider 16 | factory := ai.GetAIClientFactory() 17 | configProvider := ai.GetConfigProvider() 18 | 19 | // Use the factory to create the client 20 | aiClient := factory.NewClient(i.Backend) 21 | defer aiClient.Close() 22 | 23 | var configAI ai.AIConfiguration 24 | if err := configProvider.UnmarshalKey("ai", &configAI); err != nil { 25 | return &schemav1.QueryResponse{ 26 | Response: "", 27 | Error: &schemav1.QueryError{ 28 | Message: fmt.Sprintf("Failed to unmarshal AI configuration: %v", err), 29 | }, 30 | }, nil 31 | } 32 | 33 | var aiProvider ai.AIProvider 34 | for _, provider := range configAI.Providers { 35 | if i.Backend == provider.Name { 36 | aiProvider = provider 37 | break 38 | } 39 | } 40 | if aiProvider.Name == "" { 41 | return &schemav1.QueryResponse{ 42 | Response: "", 43 | Error: &schemav1.QueryError{ 44 | Message: fmt.Sprintf("AI provider %s not found in configuration", i.Backend), 45 | }, 46 | }, nil 47 | } 48 | 49 | // Configure the AI client 50 | if err := aiClient.Configure(&aiProvider); err != nil { 51 | return &schemav1.QueryResponse{ 52 | Response: "", 53 | Error: &schemav1.QueryError{ 54 | Message: fmt.Sprintf("Failed to configure AI client: %v", err), 55 | }, 56 | }, nil 57 | } 58 | 59 | resp, err := aiClient.GetCompletion(ctx, i.Query) 60 | var errMessage string = "" 61 | if err != nil { 62 | errMessage = err.Error() 63 | } 64 | return &schemav1.QueryResponse{ 65 | Response: resp, 66 | Error: &schemav1.QueryError{ 67 | Message: errMessage, 68 | }, 69 | }, nil 70 | } 71 | -------------------------------------------------------------------------------- /pkg/server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "go.uber.org/zap" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" 13 | ) 14 | 15 | func TestServe(t *testing.T) { 16 | logger, _ := zap.NewDevelopment() 17 | defer logger.Sync() 18 | 19 | s := &Config{ 20 | Port: "50059", 21 | Logger: logger, 22 | EnableHttp: false, 23 | } 24 | 25 | go func() { 26 | err := s.Serve() 27 | time.Sleep(time.Second * 2) 28 | assert.NoError(t, err, "Serve should not return an error") 29 | }() 30 | 31 | // Wait until the server is ready to accept connections 32 | err := waitForPort("localhost:50059", 10*time.Second) 33 | assert.NoError(t, err, "Server should start without error") 34 | 35 | conn, err := grpc.Dial("localhost:50059", grpc.WithInsecure()) 36 | assert.NoError(t, err, "Should be able to dial the server") 37 | defer conn.Close() 38 | 39 | // Test a simple gRPC reflection request 40 | cli := grpc_reflection_v1alpha.NewServerReflectionClient(conn) 41 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 42 | defer cancel() 43 | resp, err := cli.ServerReflectionInfo(ctx) 44 | assert.NoError(t, err, "Should be able to get server reflection info") 45 | assert.NotNil(t, resp, "Response should not be nil") 46 | 47 | // Cleanup 48 | err = s.Shutdown() 49 | assert.NoError(t, err, "Shutdown should not return an error") 50 | } 51 | 52 | func waitForPort(address string, timeout time.Duration) error { 53 | start := time.Now() 54 | for { 55 | conn, err := net.Dial("tcp", address) 56 | if err == nil { 57 | conn.Close() 58 | return nil 59 | } 60 | if time.Since(start) > timeout { 61 | return err 62 | } 63 | time.Sleep(100 * time.Millisecond) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "changelog-path": "CHANGELOG.md", 5 | "release-type": "go", 6 | "prerelease": false, 7 | "bump-minor-pre-major": true, 8 | "bump-patch-for-minor-pre-major": true, 9 | "draft": false, 10 | "extra-files": [ 11 | "README.md", 12 | "deploy/manifest.yaml", 13 | "chart/Chart.yaml", 14 | "chart/values.yaml", 15 | "container/manifests/deployment.yaml" 16 | ], 17 | "changelog-sections": [ 18 | { 19 | "type": "feat", 20 | "section": "Features" 21 | }, 22 | { 23 | "type": "fix", 24 | "section": "Bug Fixes" 25 | }, 26 | { 27 | "type": "chore", 28 | "section": "Other" 29 | }, 30 | { 31 | "type": "docs", 32 | "section": "Docs" 33 | }, 34 | { 35 | "type": "perf", 36 | "section": "Performance" 37 | }, 38 | { 39 | "type": "build", 40 | "hidden": true, 41 | "section": "Build" 42 | }, 43 | { 44 | "type": "deps", 45 | "section": "Dependency Updates" 46 | }, 47 | { 48 | "type": "ci", 49 | "hidden": true, 50 | "section": "CI" 51 | }, 52 | { 53 | "type": "refactor", 54 | "section": "Refactoring" 55 | }, 56 | { 57 | "type": "revert", 58 | "hidden": true, 59 | "section": "Reverts" 60 | }, 61 | { 62 | "type": "style", 63 | "hidden": true, 64 | "section": "Styling" 65 | }, 66 | { 67 | "type": "test", 68 | "hidden": true, 69 | "section": "Tests" 70 | } 71 | ] 72 | } 73 | }, 74 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" 75 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "helpers:pinGitHubActionDigests", 6 | ":gitSignOff", 7 | "group:allNonMajor" 8 | ], 9 | "addLabels": ["dependencies"], 10 | "postUpdateOptions": [ 11 | "gomodTidy", 12 | "gomodMassage" 13 | ], 14 | "automerge": true, 15 | "automergeType": "pr", 16 | "schedule": [ 17 | "at any time" 18 | ], 19 | "platformAutomerge": true, 20 | "packageRules": [ 21 | { 22 | "matchPackageNames": ["azure-sdk-for-go"], 23 | "enabled": true, 24 | "groupName": "azure-group" 25 | }, 26 | { 27 | "matchPackageNames": ["prometheus"], 28 | "enabled": true, 29 | "groupName": "prometheus-group" 30 | }, 31 | { 32 | "matchPackageNames": ["k8s.io", "sigs.k8s.io"], 33 | "enabled": true, 34 | "groupName": "kubernetes-group" 35 | }, 36 | { 37 | "matchPackageNames": ["golang"], 38 | "enabled": true, 39 | "groupName": "golang-group" 40 | }, 41 | { 42 | "matchUpdateTypes": ["minor", "patch"], 43 | "matchCurrentVersion": "!/^0/", 44 | "automerge": true 45 | }, 46 | { 47 | "matchManagers": ["gomod"], 48 | "addLabels": ["go"] 49 | }, 50 | { 51 | "matchManagers": ["github-actions"], 52 | "addLabels": ["github_actions"] 53 | }, 54 | { 55 | "matchManagers": ["dockerfile"], 56 | "addLabels": ["docker"] 57 | } 58 | ], 59 | "regexManagers": [ 60 | { 61 | "fileMatch": [ 62 | "(^|\\/)Makefile
quot;,
63 |         "(^|\\/)Dockerfile",
64 |         "(^|\\/).*\\.ya?ml
quot;,
65 |         "(^|\\/).*\\.toml
quot;,
66 |         "(^|\\/).*\\.sh
quot;
67 |       ],
68 |       "matchStrings": [
69 |         "# renovate: datasource=(?<datasource>.+?) depName=(?<depName>.+?)\\s.*?_VERSION ?(\\??=|\\: ?) ?\\\"?(?<currentValue>.+?)?\\\"?\\s"
70 |       ]
71 |     }
72 |   ]
73 | }
74 | 


--------------------------------------------------------------------------------