Compare commits

...

324 Commits

Author SHA1 Message Date
github-actions[bot]
5383d2e858 chore(main): release 0.2.5 (#302) 2023-04-25 10:21:45 +02:00
Matthis
5d6f3c31b9 Merge pull request #324 from patrickpichler/feature/323/store-cache
feat: use OS conform path for storing cached results
2023-04-25 09:48:39 +02:00
Patrick Pichler
7eddb8f4a6 feat: use OS conform path for storing cached results
Instead of storing cached values in the config yaml, they are now stored
under these OS specific locations:
* Linux: `~/.cache/k8sgpt`
* MacOS: `~/Library/Caches`
* Windows: `%LocalAppData%\cache`

Additionally a `Cache` package and interface has been introduced.
Currently there are two implementations:
* Noop - Doesn't do anything
* FileBased - Stores data in files under the locations listed above

fixes #323

Signed-off-by: Patrick Pichler <git@patrickpichler.dev>
2023-04-24 19:07:54 +02:00
Alex Jones
c3cc413e7f feat: async calls (#311)
* feat: async calls

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* feat: added concurrency settings

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

* feat: added in ability to set max concurrency

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

---------

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
Co-authored-by: Matthis <99146727+matthisholleville@users.noreply.github.com>
2023-04-24 14:04:37 +00:00
Thomas Schuetz
2391603075 feat: add subproject group to CODEOWNERS (#322)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-24 16:02:42 +02:00
renovate[bot]
e7f74db6e5 fix(deps): update module github.com/aquasecurity/trivy-operator to v0.13.1 (#321)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
2023-04-23 21:03:29 +02:00
Matthis
5aaeb77a48 Merge pull request #320 from matthisholleville/doc/update-documentation
feat: add serve & integration to README
2023-04-23 17:50:55 +02:00
Matthis
751010f25b Merge branch 'main' into doc/update-documentation 2023-04-23 17:44:09 +02:00
Matthis Holleville
a65ee7fc09 feat: add serve & integration to README
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-23 17:43:46 +02:00
Nuno Adrego
ff799825cf feat: add k8sgpt grafana dashboard (#316)
Signed-off-by: Nuno Adrego <55922671+nunoadrego@users.noreply.github.com>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
2023-04-23 16:33:57 +01:00
Ettore Di Giacinto
cf797a6eb6 feat: allow to set a baseurl (#310)
* feat: allow to set a baseURL for OpenAI providers

This allows to run local models that have a compatible OpenAI API, or
for instance use a proxy.

Signed-off-by: mudler <mudler@mocaccino.org>

* feat: allow to set baseURL in the auth subcommand

Signed-off-by: mudler <mudler@mocaccino.org>

---------

Signed-off-by: mudler <mudler@mocaccino.org>
Co-authored-by: Alex Jones <alexsimonjones@gmail.com>
Co-authored-by: Matthis <99146727+matthisholleville@users.noreply.github.com>
2023-04-21 19:04:34 +00:00
Xinwei Xiong
754bf917e1 feat: the overall optimization and architecture design of the makefile are made (#317)
* feature(makefile): The overall optimization and architecture design of the Makefile are made

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: the overall optimization and architecture design of the makefile are made

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

---------

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
Co-authored-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-21 20:02:45 +01:00
Thomas Schuetz
d0f7a1105f chore: change license to Apache-2 (#313)
* chore: change license to Apache-2

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-21 19:46:31 +02:00
Alex Jones
95305438b3 Delete demo (#315)
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-21 13:19:39 +01:00
Alex Jones
ddd830cc56 feat: update readme (#314)
No longer necessary to draw distinction between k8sgpt and kubectl ai. Also removed milestones

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-21 13:18:58 +01:00
Peter Pan
0a4ed0d907 chore: add serviceMonitor in sample yaml (#304)
Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
2023-04-21 10:10:39 +01:00
Aris Boutselis
2102f06585 Merge pull request #308 from arbreezy/feat/config-interface
feat: add configuration interface to support customer providers
2023-04-20 16:31:56 +01:00
Aris Boutselis
84a3cc05fb feat: add configuration interface to support customer providers
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-04-20 16:26:12 +01:00
Matthis
bd4ab0e589 Merge pull request #306 from matthisholleville/feat/analyze-error-handling
feat: modify analyze error handling
2023-04-20 15:53:52 +02:00
Matthis
7a7c6cf157 Merge branch 'main' into feat/analyze-error-handling 2023-04-20 15:09:52 +02:00
Matthis
fa087ff559 feat: modify error handling to return a list of errors to display to the user at the end of analysis without blocking it if an error is detected (e.g., version of an object is not available on user's cluster)
Signed-off-by: Matthis <matthish29@gmail.com>
2023-04-20 15:09:13 +02:00
Matthis
3ef6156d55 Merge pull request #300 from panpan0000/main
chore: analyze Pod ReadinessProbe faliure
2023-04-20 11:17:27 +02:00
Matthis
d50ca29a2c Merge branch 'main' into main 2023-04-20 10:52:39 +02:00
renovate[bot]
df2ed4185b fix(deps): update module github.com/prometheus/client_golang to v1.15.0 (#303)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 10:47:26 +02:00
Peter Pan
3c7e0bba1d chore: analyze Pod ReadinessProbe faliure
Signed-off-by: Peter Pan <Peter.Pan@daocloud.io>
2023-04-20 03:52:47 -04:00
renovate[bot]
0472c363a4 fix(deps): update module github.com/sashabaranov/go-openai to v1.9.0 (#298)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 07:36:07 +02:00
github-actions[bot]
03228d8e6d chore(main): release 0.2.4 (#292)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-18 16:33:43 +02:00
Rakshit Gondwal
71732037fa feat: improve HPA analyzer to check ScaleTargetRef resources (#283)
* feat: improve HPA analyzer to check ScaleTargetRef resources

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

* feat: modify tests

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

* feat: improve all ScaleTargetRef to check for resources

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

* test: add test cases for all ScaleTargetRef types

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

* refactor: use interface to avoid dupplication

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

* test: add test case for NoResourceConfiguredForScaleTargetRef

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>

---------

Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
Co-authored-by: Matthis <99146727+matthisholleville@users.noreply.github.com>
2023-04-18 15:27:06 +02:00
Aris Boutselis
a962d13654 Merge pull request #296 from matthisholleville/fix/language-toggle-bug
fix: resolve language toggle bug (issue #294)
2023-04-18 10:17:10 +01:00
Matthis
0313627848 fix: resolve language toggle bug (issue #294)
Signed-off-by: Matthis <matthish29@gmail.com>
2023-04-18 06:14:11 +02:00
Matthis
3d684a2af7 fix: deployment/cronjob namespace filtering (#290)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-17 17:37:19 +02:00
Matthis
af8b350520 fix: ensure parent directories are created in EnsureDirExists function (#293) 2023-04-17 14:38:43 +01:00
Matthis
3c4bc1a92c Merge pull request #287 from matthisholleville/feature/server-logs
feat: init logging middleware on server mode
2023-04-17 11:53:12 +02:00
Matthis
5f3244a5d4 Merge branch 'main' into feature/server-logs 2023-04-17 11:46:58 +02:00
Alex Jones
222e0873c0 Update README.md (#288)
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-17 06:49:45 +01:00
Matthis Holleville
6742410025 feat: init logging middleware on server mode
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-16 23:40:19 +02:00
Alex Jones
64f359c428 Merge pull request #271 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.2.3
2023-04-16 12:50:00 +01:00
github-actions[bot]
1acb22efdb chore(main): release 0.2.3 2023-04-16 11:17:51 +00:00
Alex Jones
8615ea28ed Merge pull request #236 from patrickpichler/feature/235/use-xdg-config-home
feat: store config in XDG conform location
2023-04-16 12:17:09 +01:00
Alex Jones
a7cff482a8 Merge branch 'main' into feature/235/use-xdg-config-home
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-16 12:10:05 +01:00
Matthis
6e7c583aec Merge pull request #284 from matthisholleville/refactor/output-analysis
feat: add output query param on serve mode & refactor output logic
2023-04-16 09:24:33 +02:00
Matthis Holleville
9121a983e5 feat: rename server/main.go to server/server.go
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-16 09:20:49 +02:00
Matthis Holleville
9642202ed1 feat: add output query param on serve mode & refactor output logic
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-15 21:55:46 +02:00
Alex Jones
0f88edf4e3 Merge pull request #280 from k8sgpt-ai/feat/manifests 2023-04-15 18:19:12 +01:00
Alex Jones
e5a8c57877 Merge branch 'main' into feat/manifests 2023-04-15 18:06:12 +01:00
Alex Jones
842f08c655 feat: running in cluster
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
3988eb2fd0 feat: running in cluster
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
f0a0c9aebf chore: updated
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Alex Jones
ec2e7703c6 adding manifest example
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 18:05:57 +01:00
Matthis
a3becc9906 feat: add server metrics (#273)
* feat: expose metrics path & init analyzer errors metrics

This commit add metrics path & the analyzer error metrics in the codebase. The changes have been made across all analyzers and include the addition of a new metric with label values for the analyzer's name, analyzed object's name, and namespace. The metric's value is set to the length of the analyzer objects failures.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: add metric to cronjob & deployment & netpol

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: expose metric to NodeAnalyzer

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

---------

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-15 19:03:37 +02:00
Alex Jones
ffde363588 Merge branch 'main' into feat/manifests 2023-04-15 16:59:13 +01:00
Alex Jones
03a95e7b2a Merge pull request #282 from matthisholleville/fix/duplicated-vulnerabilityreport-filters
fix: resolve issue with duplicated integration filters.
2023-04-15 16:28:16 +01:00
Alex Jones
56a323c129 Merge branch 'main' into fix/duplicated-vulnerabilityreport-filters 2023-04-15 16:28:11 +01:00
Alex Jones
f20c139b1c Merge pull request #281 from k8sgpt-ai/feat/server-envs
feat: envs to initialise server
2023-04-15 16:27:33 +01:00
Matthis Holleville
960ba568d0 fix: resolve issue with duplicated integration filters.
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-15 15:49:39 +02:00
Alex Jones
0071e25992 feat: envs to initialise server
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 12:26:51 +01:00
Patrick Pichler
dee435514d feat: switch config file to XDG conform location
The config file is now located in an folder according to the XDG
specification (`XDG_CONFIG_HOME`).

Migration is performed automatically.

This fixes #235.

Signed-off-by: Patrick Pichler <git@patrickpichler.dev>
2023-04-15 13:18:30 +02:00
Alex Jones
fe2c08cf72 feat: wip blocked until we have envs
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-15 12:08:43 +01:00
renovate[bot]
51b1b352ac fix(deps): update module github.com/sashabaranov/go-openai to v1.8.0 (#277)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-15 07:57:50 +02:00
Alex Jones
bbf159455a Merge pull request #274 from matthisholleville/fix/server-authentication
fix: use the aiProvider object when launching the server
2023-04-14 16:33:25 +01:00
Matthis Holleville
e7076ed609 fix: use the aiProvider object when launching the server instead of the deprecated configuration keys
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-14 17:23:12 +02:00
Dominik Augustin
6247a1c0f3 feat: add node analyzer (#272)
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-14 15:08:47 +02:00
Alex Jones
a8f8070e16 Merge pull request #270 from nunoadrego/fix/version
fix: add new line after version cmd output
2023-04-14 12:12:18 +01:00
Nuno Adrego
92e7b3d3fb fix: add new line after version cmd output
Signed-off-by: Nuno Adrego <55922671+nunoadrego@users.noreply.github.com>
2023-04-14 11:41:55 +01:00
Alex Jones
763b8b92df Merge pull request #251 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.2.2
2023-04-14 10:39:48 +01:00
github-actions[bot]
ff77e64b71 chore(main): release 0.2.2 2023-04-14 08:55:50 +00:00
Alex Jones
b726e1e706 Merge pull request #253 from hdm23061993/docs/issue-232
docs: fix Slack link
2023-04-14 09:54:58 +01:00
Alex Jones
6ca80abae8 Merge branch 'main' into docs/issue-232 2023-04-14 09:54:35 +01:00
dependabot[bot]
7d1e2acaf3 chore(deps): bump github.com/docker/docker (#268)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 23.0.1+incompatible to 23.0.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v23.0.1...v23.0.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-14 10:23:37 +02:00
Alex Jones
584201a34e Merge pull request #267 from k8sgpt-ai/feat/additional-analyzers 2023-04-14 09:17:10 +01:00
Alex Jones
ddb51c7af4 chore: removing field
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-14 09:14:45 +01:00
Alex Jones
19e1b94e7c feat: anoymization based on pr feedback
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-14 07:40:27 +01:00
Alex Jones
4d3624830f chore: Merge branch 'main' into feat/additional-analyzers 2023-04-14 07:39:28 +01:00
Alex Jones
fe529510b6 feat: anoymization based on pr feedback
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-14 07:39:05 +01:00
Alex Jones
f9b25d9e85 chore: fixing up tests
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-13 21:48:33 +01:00
Alex Jones
498d454c17 chore: fixing up tests
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-13 21:44:33 +01:00
Alex Jones
23071fd2e6 chore: additional analyzers
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-13 21:25:41 +01:00
renovate[bot]
0af34a1a95 chore(deps): update actions/checkout digest to 8e5e7e5 (#266)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-13 22:10:53 +02:00
Alex Jones
5dcc19038f Merge pull request #254 from k8sgpt-ai/feat/serve
feat: http server
2023-04-13 13:24:56 +01:00
Thomas Schuetz
26c0cb2eed feat: add simple health endpoint
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-13 13:29:19 +02:00
Thomas Schuetz
336ec2a426 fix: bool conversion
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-13 13:13:45 +02:00
Thomas Schuetz
6b630275eb fix: start message
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-13 13:11:39 +02:00
Thomas Schuetz
ddf3561105 Merge branch 'main' of github.com:k8sgpt-ai/k8sgpt into feat/serve 2023-04-13 13:08:27 +02:00
Harshit Mehta
a3883f0aba Merge branch 'main' into docs/issue-232 2023-04-13 13:43:53 +05:30
Alex Jones
7551f8bf03 Merge pull request #260 from hdm23061993/feat/issue-256
feat: check for auth only in case of --explain
2023-04-13 09:08:36 +01:00
Thomas Schuetz
159b3851ec fix: naming
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-13 09:00:14 +02:00
Alex Jones
1356165e18 Merge branch 'main' into feat/issue-256 2023-04-13 07:56:43 +01:00
Alex Jones
4c5cc3df9d Merge pull request #258 from k8sgpt-ai/renovate/helm.sh-helm-v3-3.x
fix(deps): update module helm.sh/helm/v3 to v3.11.3
2023-04-13 07:40:15 +01:00
Alex Jones
381402bc27 Merge branch 'main' into renovate/helm.sh-helm-v3-3.x 2023-04-13 07:39:16 +01:00
Harshit Mehta
57790e5bc7 feat: check for auth only in case of --explain
Signed-off-by: Harshit Mehta <harshitm@nvidia.com>
2023-04-13 11:54:21 +05:30
Alex Jones
3517d76479 Merge pull request #259 from k8sgpt-ai/chore/oidc
chore: added oidc
2023-04-13 07:19:41 +01:00
Alex Jones
bffad41134 chore: added oidc
Signed-off-by: Alex Jones <alex@Alexs-MBP.lan>
2023-04-13 07:18:19 +01:00
renovate[bot]
4dd91ed826 fix(deps): update module helm.sh/helm/v3 to v3.11.3
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-13 06:13:06 +00:00
Alex Jones
fef5e17d31 Merge pull request #257 from k8sgpt-ai/renovate/actions-checkout-digest
chore(deps): update actions/checkout digest to 83b7061
2023-04-13 07:12:11 +01:00
Thomas Schuetz
9157d4dd13 feat: unified cmd and api
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-13 08:11:56 +02:00
renovate[bot]
cbe6f27c05 chore(deps): update actions/checkout digest to 83b7061
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-12 19:45:08 +00:00
Harshit Mehta
1dccaea3f4 docs: fix Slack link
Signed-off-by: Harshit Mehta <harshitm@nvidia.com>
2023-04-12 20:06:25 +05:30
Thomas Schuetz
adae2ef71d feat: updated api
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-12 16:30:09 +02:00
Thomas Schuetz
b2e8adda33 feat: first version of serve
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-12 15:05:01 +02:00
renovate[bot]
13c9231aaf chore(deps): update module oras.land/oras-go to v1.2.3 (#249)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
2023-04-12 14:40:52 +02:00
Alex Jones
5d87b27a3e Merge pull request #223 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.2.1
2023-04-12 10:52:27 +01:00
github-actions[bot]
668f8a63fa chore(main): release 0.2.1 2023-04-12 09:51:37 +00:00
Alex Jones
fb7543418b Merge pull request #242 from matthisholleville/feature/add-anonymize-option
feat: add anonymize option
2023-04-12 10:50:38 +01:00
Matthis Holleville
c0afc0f5c9 feat: refactor integration to use Failure object
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-12 11:40:51 +02:00
Alex Jones
cfce828fd1 Merge pull request #243 from k8sgpt-ai/feat/integrate
feat: integrations
2023-04-12 08:58:20 +01:00
Alex Jones
096321b31a chore: merged
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-12 08:58:07 +01:00
Alex Jones
ca79ef9986 Merge pull request #247 from k8sgpt-ai/renovate/kubernetes-go
fix(deps): update kubernetes packages to v0.27.0
2023-04-12 08:48:44 +01:00
Alex Jones
0328110f11 Merge branch 'main' into renovate/kubernetes-go 2023-04-12 08:48:35 +01:00
Alex Jones
8b5586901c Merge pull request #244 from chetanguptaa/analysis_test
fix: updated analysis_test.go
2023-04-12 08:48:11 +01:00
Alex Jones
0cf5eab988 Merge branch 'main' into analysis_test 2023-04-12 08:47:30 +01:00
chetan gupta
825e9a43bd chore: updated analysis_test.go
Signed-off-by: chetan gupta <chetangupta123raj@gmail.com>
2023-04-12 07:50:06 +05:30
renovate[bot]
7a97034cf4 fix(deps): update kubernetes packages to v0.27.0
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-11 23:30:13 +00:00
Alex Jones
5e5d4b6de1 chore: updating based on feedback
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 21:17:55 +01:00
Alex Jones
34e3e3912e Merge branch 'main' into feat/integrate 2023-04-11 20:53:10 +01:00
Alex Jones
8e48f6c6cf Merge pull request #246 from k8sgpt-ai/renovate/google-github-actions-release-please-action-digest 2023-04-11 20:38:42 +01:00
Alex Jones
ced0de6448 Merge branch 'main' into feat/integrate
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:24:30 +01:00
Alex Jones
fabe01aa01 chore: weird new line after filter removed
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:23:34 +01:00
Alex Jones
258c69a17c chore: fixing filters
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
4d20f70fb4 chore: fixing filters
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
1b7f4ce44a chore: updated link output
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
3682f5c7eb feat: integration ready for first review
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
c809af3f47 chore: Fixing broken tests
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
renovate[bot]
55dda432ab chore(deps): update google-github-actions/release-please-action digest to f7edb9e (#241)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Thomas Stadler
428c348586 chore: fix mistake introduced by ab55f157 (#240)
Signed-off-by: Thomas Stadler <thomas@thomasst.xyz>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
db1388fd20 chore: adding k8sgpt-approvers (#238)
* chore: adding k8sgpt-approvers

* Update CODEOWNERS

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

---------

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Thomas Stadler
fe261b375f fix: exit progressbar on error (#99)
Signed-off-by: Thomas Stadler <thomas@thomasst.xyz>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Aris Boutselis
b45ff1aa8e docs: add statefulSet analyzer in the docs. (#233)
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:12 +01:00
Alex Jones
4984840de1 wip
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:06 +01:00
Alex Jones
80ac51c804 chore: compiling successfully
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:06 +01:00
AlexsJones
b0e517006e feat: initial impl of integration
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:06 +01:00
AlexsJones
61d6e52465 feat: initial impl of integration
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-11 19:22:06 +01:00
Alex Jones
a62271661d Merge branch 'main' into renovate/google-github-actions-release-please-action-digest 2023-04-11 19:21:40 +01:00
HOLLEVILLE Matthis
45c7ecf98b Merge branch 'main' into feature/add-anonymize-option 2023-04-11 20:19:12 +02:00
Alex Jones
370e13b1a5 Merge pull request #245 from matthisholleville/feat/log-on-unexisted-filters
feat: return errors if filter specified by flag does not exist.
2023-04-11 19:18:28 +01:00
renovate[bot]
a1d8012a5c chore(deps): update google-github-actions/release-please-action digest to c078ea3
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-11 17:38:25 +00:00
Matthis Holleville
dd5824f436 feat: return errors if filter specified by flag does not exist.
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-11 19:21:15 +02:00
Matthis Holleville
08f2a89e54 feat: improve security of the MaskString function
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-11 19:12:59 +02:00
Matthis Holleville
6f0865413f feat: improve documentation
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-11 15:23:52 +02:00
Matthis Holleville
b687473e61 feat: add more details on anonymize flag
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-11 13:41:59 +02:00
Matthis Holleville
11326c1c5f feat: improve documentation & update hpa message
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-11 13:28:05 +02:00
Thomas Schuetz
fe502e1135 Merge branch 'main' into feature/add-anonymize-option 2023-04-11 07:47:06 +02:00
renovate[bot]
21dc61c04f chore(deps): update google-github-actions/release-please-action digest to f7edb9e (#241)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-11 07:36:58 +02:00
Matthis Holleville
705d2a0dce fix: pdb test
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-10 23:44:16 +02:00
Matthis Holleville
fd936ceaf7 fix: improve ReplaceIfMatch regex
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-10 23:36:43 +02:00
Matthis Holleville
8a60b57940 feat: add anonymization example to README
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-10 23:14:18 +02:00
Matthis Holleville
30e33495e0 Merge branch 'main' into feature/add-anonymize-option 2023-04-10 23:12:30 +02:00
Matthis Holleville
d2a84ea2b5 feat: add anonymization flag
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-10 23:12:02 +02:00
Thomas Stadler
3845d4747f chore: fix mistake introduced by ab55f157 (#240)
Signed-off-by: Thomas Stadler <thomas@thomasst.xyz>
2023-04-10 22:30:10 +02:00
Alex Jones
992b107c2d chore: adding k8sgpt-approvers (#238)
* chore: adding k8sgpt-approvers

* Update CODEOWNERS

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>

---------

Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
Co-authored-by: Thomas Schuetz <38893055+thschue@users.noreply.github.com>
2023-04-10 21:25:45 +02:00
Alex Jones
63dccdbe6f Merge pull request #237 from thomasstxyz/fix/missing-newline-after-progressbar
fix: exit progressbar on error to fix missing newline
2023-04-10 19:44:01 +01:00
Thomas Stadler
ab55f157ef fix: exit progressbar on error (#99)
Signed-off-by: Thomas Stadler <thomas@thomasst.xyz>
2023-04-10 20:01:55 +02:00
Aris Boutselis
ba01bd4b6e docs: add statefulSet analyzer in the docs. (#233)
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-04-10 13:54:51 +02:00
HOLLEVILLE Matthis
a582d444c5 fix: use hpa namespace instead analyzer namespace (#230)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-10 07:49:22 +02:00
renovate[bot]
9423b53c1d chore(deps): update anchore/sbom-action action to v0.14.1 (#228)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-09 10:11:15 +02:00
renovate[bot]
5f3a5a54a0 fix(deps): update module github.com/sashabaranov/go-openai to v1.7.0 (#227)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-09 07:28:42 +02:00
Alex Jones
83d8571206 Merge pull request #226 from arbreezy/feat/statefulset-check-sc
feat: add storage class names' check.
2023-04-08 08:53:59 +01:00
Aris Boutselis
c8ba7d62d2 feat: add storage class names' check.
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-04-07 21:39:14 +01:00
Thomas Schuetz
286983105d ci: run on pr (main) (#222)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-07 15:25:21 +01:00
Thomas Schuetz
e449cb6023 chore: add fakeai provider (#218)
* chore: add fakeai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* chore: add fakeai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: name of openai provider

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: renamed fakeai backend to noopai

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: renamed fakeai backend to noopai

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
Co-authored-by: Alex Jones <alexsimonjones@gmail.com>
2023-04-06 21:10:01 +02:00
HOLLEVILLE Matthis
1069f0c610 Merge pull request #221 from arbreezy/feat/statefulset-analyzer 2023-04-06 16:27:52 +02:00
Aris Boutselis
c041ce2bbb feat: introduce StatefulSet analyser.
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-04-06 13:31:58 +01:00
Alex Jones
5b098fd0d8 Merge pull request #204 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.2.0
2023-04-05 20:56:24 +01:00
github-actions[bot]
580ba52dce chore(main): release 0.2.0 2023-04-05 19:07:35 +00:00
Alex Jones
ddef9fa987 Merge pull request #220 from k8sgpt-ai/fix/dynamic-filters
fix: regression on dynamic filters
2023-04-05 20:07:00 +01:00
AlexsJones
93bcc627ba fix: regression on dynamic filters
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-05 20:05:17 +01:00
Alex Jones
b6090ed2c0 Merge pull request #215 from matthisholleville/feature/configuration-new-format
feat!: add support for new configuration format
2023-04-05 19:29:25 +01:00
Alex Jones
cff69a3926 Merge branch 'main' into feature/configuration-new-format 2023-04-05 19:15:35 +01:00
Alex Jones
bfa7f32474 Merge pull request #219 from k8sgpt-ai/renovate/github.com-stretchr-testify-1.x
fix(deps): update module github.com/stretchr/testify to v1.8.2
2023-04-05 19:09:02 +01:00
renovate[bot]
f5e3ca0bca fix(deps): update module github.com/stretchr/testify to v1.8.2
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-05 16:59:35 +00:00
Alex Jones
a96d74371f Merge pull request #211 from matthisholleville/feature/add-tests-to-hpa-analyzer 2023-04-05 17:58:56 +01:00
HOLLEVILLE Matthis
7429b933c0 Merge branch 'main' into feature/configuration-new-format
Signed-off-by: HOLLEVILLE Matthis <99146727+matthisholleville@users.noreply.github.com>
2023-04-05 16:10:34 +02:00
HOLLEVILLE Matthis
337c4a616b Merge branch 'main' into feature/add-tests-to-hpa-analyzer 2023-04-05 16:07:28 +02:00
Matthis Holleville
ba4d701681 fix: Spelling
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-05 16:06:35 +02:00
Alex Jones
1f0e9f2445 Merge pull request #217 from k8sgpt-ai/chore/json-output
chore: prettier json output and output improvements
2023-04-05 14:58:25 +01:00
Thomas Schuetz
2f21002899 fix: details in json output
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-05 15:17:06 +02:00
Thomas Schuetz
22e31661bf chore: added initial tests for json output
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-05 14:08:23 +02:00
Thomas Schuetz
db40734a0d chore: made json output prettier and improved output
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-05 13:36:30 +02:00
Matthis Holleville
9b243cdcaa feat: add support for new configuration format
This commit adds support for a new configuration format that is not backwards compatible with the previous format. This is a breaking change and requires users to update their configuration files to use the new format.

BREAKING CHANGE: The format of the configuration file has changed. Users must update their configuration files to use the new format.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-05 09:39:21 +02:00
Matthis Holleville
a24d1f1b30 fix: fixed hpa tests after rebase
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-05 08:53:49 +02:00
Thomas Schuetz
8958a45b9b Merge branch 'main' into feature/add-tests-to-hpa-analyzer 2023-04-05 08:40:35 +02:00
Thomas Schuetz
0195bfab30 chore: analyzer and ai interfacing (#200)
* chore: analyzer and ai interfacing

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: backend variable for aiProvider in cmd

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: changed folders for analyzers

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* chore: renamed analyzers

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: fixed ingress tests after rebase

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: fixed ingress tests after rebase

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-05 08:40:12 +02:00
Thomas Schuetz
d105a20677 Merge branch 'main' into feature/add-tests-to-hpa-analyzer 2023-04-05 07:51:52 +02:00
renovate[bot]
eeac731858 fix(deps): update module github.com/sashabaranov/go-openai to v1.6.1 (#207)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-05 07:17:45 +02:00
Alex Jones
1abfe8dbca Merge pull request #213 from k8sgpt-ai/renovate/golang-1.x 2023-04-05 05:57:37 +01:00
Alex Jones
df4ee2ad1a Merge pull request #214 from k8sgpt-ai/renovate/golang.org-x-term-0.x 2023-04-05 05:57:21 +01:00
renovate[bot]
8ab3573e13 fix(deps): update module golang.org/x/term to v0.7.0
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-05 03:00:50 +00:00
renovate[bot]
e9994b8d16 chore(deps): update golang docker tag to v1.20.3
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-04 23:52:08 +00:00
Matthis Holleville
5a59abb55d feat: add tests for hpa analyzer
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-04 22:59:26 +02:00
Alex Jones
f65f0f5a5f Merge pull request #205 from rakshitgondwal/docs
docs: add installation guide using rpm, deb, apk
2023-04-04 20:15:24 +01:00
Alex Jones
1fff4035ea Merge branch 'main' into docs 2023-04-04 20:07:27 +01:00
Alex Jones
ba1352093b Merge pull request #206 from matthisholleville/feature/add-tests-to-ingress-analyzer 2023-04-04 17:51:49 +01:00
Alex Jones
b46224f297 Merge pull request #208 from k8sgpt-ai/renovate/github.com-spf13-cobra-1.x 2023-04-04 17:50:38 +01:00
renovate[bot]
5d5e082f41 fix(deps): update module github.com/spf13/cobra to v1.7.0
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-04 15:22:27 +00:00
Rakshit Gondwal
184920988f docs: add curl command and release-please annoations
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-04 18:24:59 +05:30
Rakshit Gondwal
53c1330538 docs: minor change
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-04 16:24:55 +05:30
Rakshit Gondwal
ddc120e7c2 docs: add guide to details block
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-04 16:23:21 +05:30
Matthis Holleville
e27e9409dc feat: add tests for ingress analyzer && Use t.Fatalf to report a fatal error if RunAnalysis returns an unexpected error
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-04 01:08:53 +02:00
Rakshit Gondwal
fc47c58ae1 docs: modify README
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-03 19:01:38 +05:30
Rakshit Gondwal
0f46ceb445 docs: modify README
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-03 18:54:47 +05:30
Rakshit Gondwal
8e4ce6a974 docs: add installation guide via packages
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-03 18:52:56 +05:30
renovate[bot]
9ff3fbc382 chore(deps): pin anchore/sbom-action action to 448520c (#203)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 12:03:49 +02:00
github-actions[bot]
4c773175cd chore(main): release 0.1.8 (#192)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-03 10:55:55 +02:00
Thomas Schuetz
67753be6f3 chore: create linux packages (#201)
* chore: build deb, rpm, apk packages

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* fix: IMAGE NAME

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-03 10:54:40 +02:00
HOLLEVILLE Matthis
075a940d2c feat: add password flag for backend authentication (#199)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-04-03 10:12:59 +02:00
renovate[bot]
f8291aab08 chore(deps): pin dependencies (#198)
Signed-off-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-02 23:09:52 +02:00
Alex Jones
622bf5824b Merge pull request #197 from k8sgpt-ai/feat/unit-testing-example 2023-04-02 21:56:09 +01:00
AlexsJones
5f30a4ddf4 feat: test workflow
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:29:52 +01:00
AlexsJones
d5bc91fa8a test workflow
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:29:08 +01:00
AlexsJones
44cc8f7ad6 feat: service test
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:24:04 +01:00
AlexsJones
35b838bfaf feat: adding unit testing and example
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 21:17:05 +01:00
Alex Jones
b6436378e1 Merge pull request #196 from k8sgpt-ai/feat/analyzer-ifacing-example
feat: analyzer ifacing
2023-04-02 20:50:44 +01:00
Thomas Schuetz
508b5c37e1 Merge branch 'main' into feat/analyzer-ifacing-example 2023-04-02 21:46:39 +02:00
Alex Jones
124e46e4ed Merge pull request #195 from k8sgpt-ai/renovate/github.com-sashabaranov-go-openai-1.x 2023-04-02 20:41:39 +01:00
AlexsJones
426f562be8 feat: analyzer ifacing
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 20:32:44 +01:00
renovate[bot]
91fb06530a fix(deps): update module github.com/sashabaranov/go-openai to v1.5.8
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-02 16:38:47 +00:00
Alex Jones
bbbbd851df Merge pull request #194 from rakshitgondwal/dev 2023-04-02 17:14:14 +01:00
Rakshit Gondwal
dde4e833b0 feat: alias filter to filters
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
2023-04-02 21:35:15 +05:30
Alex Jones
5986f4f840 Merge pull request #191 from k8sgpt-ai/feat/shields
feat: adding shields to readme
2023-04-02 14:35:24 +01:00
AlexsJones
213ecd8e83 feat: adding shields to readme
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-02 14:34:36 +01:00
Alex Jones
6ead5b356d Merge pull request #173 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.7
2023-04-02 14:26:40 +01:00
github-actions[bot]
c733292b92 chore(main): release 0.1.7 2023-04-02 13:14:01 +00:00
Alex Jones
3c95a5db82 Merge pull request #186 from yeahservice/feature/pdb-analyzer
feat: add pdb analyzer
2023-04-02 14:13:21 +01:00
Alex Jones
ea1ca44dba Merge branch 'main' into feature/pdb-analyzer 2023-04-02 14:11:46 +01:00
Alex Jones
33319b8ef5 Merge pull request #189 from yeahservice/fix/hpa-analyzer-parent-object
fix: hpaAnalyzer analysis result is using wrong parent
2023-04-02 14:05:49 +01:00
Dominik Augustin
1190fe60fd fix: hpaAnalyzer analysis result is using wrong parent
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 15:05:07 +02:00
Dominik Augustin
ceff0084df fix: spelling of PodDisruptionBudget
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:57:24 +02:00
Dominik Augustin
f6974d0758 docs: add pdbAnalyzer as optional analyzer
Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:50:03 +02:00
Dominik Augustin
532a5ce033 feat: add pda analyzer
Adds a PodDisruptionBudget analyzer, that checks if PDBs have matching
pods with their defined selector.

Signed-off-by: Dominik Augustin <dom.augustin@gmx.at>
2023-04-02 14:38:39 +02:00
AlexsJones
071ee560f3 chore: merge branch 'chetanguptaa-some-fixes' 2023-04-01 17:23:10 +01:00
AlexsJones
e1d89920b0 chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:50 +01:00
AlexsJones
96d0d754ea chore: removes bar on normal analyze events
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-04-01 17:22:37 +01:00
chetan gupta
86e9564aaf Merge remote-tracking branch 'origin/some-fixes' into some-fixes 2023-04-01 21:30:04 +05:30
chetan gupta
9cef52fb8e Merge branch 'main' of https://github.com/k8sgpt-ai/k8sgpt into some-fixes
docs: updated README.md
2023-04-01 21:24:20 +05:30
chetan gupta
9915f9406b Merge branch 'main' into some-fixes 2023-04-01 21:15:26 +05:30
chetan gupta
a24a8da246 updated README.md
Signed-off-by: chetan gupta <chetangupta123raj@gmail.com>
2023-04-01 21:14:07 +05:30
Alex Jones
f337f6b447 Merge pull request #177 from k8sgpt-ai/renovate/anchore-sbom-action-0.x
chore(deps): update anchore/sbom-action action to v0.14.1
2023-04-01 16:33:54 +01:00
renovate[bot]
80f29dae4f chore(deps): update anchore/sbom-action action to v0.14.1
Signed-off-by: Renovate Bot <bot@renovateapp.com>
2023-04-01 15:24:01 +00:00
Alex Jones
a76d60a652 Merge pull request #171 from matthisholleville/feature/hpa-analyzer
feat: add hpa analyzer and init additionalAnalyzers
2023-04-01 16:23:39 +01:00
Alex Jones
dea3ad9ebf Merge branch 'main' into feature/hpa-analyzer 2023-04-01 16:22:44 +01:00
Roberth Strand
207f26432c Merge pull request #175 from k8sgpt-ai/101-need-more-space-between-issues-with-explain
refactor: 101 need more space between issues with explain
2023-04-01 16:27:45 +02:00
Roberth Strand
3e836d81b7 refactor: merged main into branch
Merged origin/main into the branch to get the PR up to date and mergable.

Closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-04-01 11:36:15 +02:00
Matthis Holleville
006751567f Merge branch 'main' into feature/hpa-analyzer 2023-03-31 23:25:07 +02:00
Thomas Schuetz
9d9c26214f chore: update dependencies (#174)
* chore: refine renovate config

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

* chore: update dependencies

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>

---------

Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-31 22:02:54 +02:00
Thomas Schuetz
d23da9ae83 chore: refine renovate config (#172)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-31 21:53:03 +02:00
Matthis Holleville
5dad75fbe9 feat: check if ScaleTargetRef is possible option
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:47:10 +02:00
HOLLEVILLE Matthis
4916fef9d6 fix: update client API call to use StatefulSet instead of Deployment
Co-authored-by: Dominik Augustin <yeahservice@users.noreply.github.com>
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 19:39:17 +02:00
Matthis Holleville
360387249f feat: add hpa analyzer and init additionalAnalyzers
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 18:22:01 +02:00
Alex Jones
0e3ef8dd27 Merge pull request #170 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.6
2023-03-31 15:42:56 +01:00
github-actions[bot]
825ad538fe chore(main): release 0.1.6 2023-03-31 14:42:48 +00:00
Alex Jones
089059675b Merge pull request #169 from matthisholleville/fix/explain-not-displayed
fix: analysis detail not displayed when --explain
2023-03-31 15:42:20 +01:00
Matthis Holleville
869ba90907 fix: analysis detail not displayed when --explain
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 16:39:28 +02:00
Alex Jones
391c3fa7a8 Merge pull request #160 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.5
2023-03-31 15:08:22 +01:00
github-actions[bot]
8e99076497 chore(main): release 0.1.5 2023-03-31 14:04:36 +00:00
Alex Jones
c2b8732a5c Merge pull request #161 from matthisholleville/feature/add-and-remove-filters
feat: add & remove default filter(s) to analyze.
2023-03-31 15:04:01 +01:00
Matthis Holleville
3ed545f33f feat: rework filters
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 15:46:20 +02:00
Matthis Holleville
975813d328 feat: check if filters does not empty on add & remove
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 13:57:03 +02:00
Matthis Holleville
9aa0e8960e feat: update filters add & remove to be more consistent
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 13:30:59 +02:00
Matthis Holleville
0a124484a2 fix: spelling on dupplicateFilters
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 13:02:26 +02:00
Matthis Holleville
30faf84254 feat: remove filter prefix on subcommand
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 12:59:19 +02:00
Roberth Strand
c0aebb21a0 refactor: added newline to explanation
The newline at the end of the explanation will make is so that the wall of text that you get with several issues get a bit of space. This does not effect the JSON output, only the regular output.

closes #101

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-03-31 11:23:35 +02:00
Matthis Holleville
9ebb7de3c0 Merge branch 'main' into feature/add-and-remove-filters 2023-03-31 11:19:40 +02:00
Roberth Strand
9e9cd7083d Merge pull request #166 from matthisholleville/fix/kubecontext-has-no-effect
fix: kubecontext flag has no effect
2023-03-31 11:15:58 +02:00
Matthis Holleville
a8bf45134f fix: kubecontext flag has no effect
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-31 10:41:50 +02:00
Alex Jones
4d5449f97a Merge pull request #165 from k8sgpt-ai/chore/filterlist-file
chore: renamed filter list file
2023-03-31 09:07:39 +01:00
AlexsJones
25f8dc390c chore: renamed filter list file
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-03-31 09:07:08 +01:00
Matthis Holleville
32ddf6691c feat: add & remove default filter(s) to analyze.
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-30 23:18:43 +02:00
HOLLEVILLE Matthis
6e17c9e285 feat: add filter command add "list" subcommand (#159)
* feat: add filter command add "list" subcommand to display available filters

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: create specific file to filterList

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

* feat: rename ListAnalyzers to ListFilters

Signed-off-by: Matthis Holleville <matthish29@gmail.com>

---------

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-30 20:58:41 +02:00
Alex Jones
9f0cb6240e Merge pull request #152 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.4
2023-03-30 13:42:14 +01:00
github-actions[bot]
26db0f6941 chore(main): release 0.1.4 2023-03-30 12:29:37 +00:00
Alex Jones
2acab541bc Merge pull request #155 from k8sgpt-ai/fix/kubectx
fix: now supports different kubeconfig and kubectx
2023-03-30 13:29:05 +01:00
Thomas Schuetz
53a19bb04d Merge branch 'main' into fix/kubectx 2023-03-30 14:27:56 +02:00
Alex Jones
c8f3c946b0 fix: now supports different kubeconfig and kubectx
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-03-30 12:49:02 +01:00
HOLLEVILLE Matthis
b061566404 feat: add Ingress class validation (#154)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-30 13:48:53 +02:00
Alexandre Steppé
be061da5b6 feat: output selected backend (#153)
Signed-off-by: Alexandre Steppé <alexandre.steppe@gmail.com>
2023-03-30 13:37:10 +02:00
Roberth Strand
5f548421c7 Merge pull request #151 from k8sgpt-ai/feat/rs-cleanup
refactor: removed sample flag
2023-03-30 12:04:40 +02:00
Roberth Strand
0afd52844b refactor: removed sample flag
Commented out the sample flag that the framework comes with, as it was not in use. Choose to comment it out for now for easy reference, just in case we want to implement that type of toggle flags.

Signed-off-by: Roberth Strand <me@robstr.dev>
2023-03-30 11:48:43 +02:00
Alex Jones
f95c1e1d8c Merge pull request #130 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.3
2023-03-30 09:00:17 +01:00
github-actions[bot]
3fc0d45823 chore(main): release 0.1.3 2023-03-30 07:51:31 +00:00
Alex Jones
cf633b606d Merge pull request #144 from matthisholleville/fix/ingress-object-meta-value
fix: ingress object meta value
2023-03-30 08:51:03 +01:00
Alex Jones
ab63bd0dd1 Merge branch 'main' into fix/ingress-object-meta-value 2023-03-30 08:49:48 +01:00
Alex Jones
3e05eb2af2 Merge pull request #149 from k8sgpt-ai/feat/analysis-speed
feat: analysis speed
2023-03-30 08:44:03 +01:00
Alex Jones
548039ebe6 feat: improvement to analysis speed
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-03-30 08:19:35 +01:00
Alex Jones
0d5b8ad027 Merge branch 'main' of github.com:k8sgpt-ai/k8sgpt into main 2023-03-30 07:52:24 +01:00
Thomas Schuetz
0f910e497a Merge branch 'main' into fix/ingress-object-meta-value 2023-03-30 08:38:45 +02:00
Alex Jones
172c2df6c5 fix: bugfix for output (#148) 2023-03-30 08:38:11 +02:00
Alex Jones
2eab0c544f feat: bugfix for output
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-03-30 07:28:42 +01:00
Chadi Laoulaou
f4765bed1b fix: typo in description of the filter flag in analyze command (#147)
Signed-off-by: Chadi Laoulaou <chadilaoulaou@gmail.com>
2023-03-30 05:38:47 +02:00
Matthis Holleville
14ba8d5550 fix: add Ingress in GetParent switch case
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-30 01:35:09 +02:00
Aris Boutselis
bf49a51c62 fix: Change ObjectMeta value in Ingress analyser.
Signed-off-by: Aris Boutselis <aris.boutselis@senseon.io>
2023-03-30 00:10:19 +01:00
HOLLEVILLE Matthis
86c7e81e18 feat: add secret validation to ingress analyzer (#141)
This commit adds a check to the ingress analyzer that verifies whether the secret declared in the ingress exists on the cluster. This helps to ensure that only valid secrets are used in the ingress configuration.

Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-29 20:36:50 +00:00
Mike Levan
cdc7bb1272 readme update to specifiy WSL and Linux (#142)
Co-authored-by: michael levan <michael.levan@orbica.world>
2023-03-29 21:42:56 +02:00
HOLLEVILLE Matthis
fe683b71b8 feat: init ingress analyzer (#138)
Signed-off-by: Matthis Holleville <matthish29@gmail.com>
2023-03-29 18:27:05 +02:00
Sudipto Baral
cad2b38d03 docs: add new slack link (#134) (#135)
Signed-off-by: Sudipto Baral <sudiptobaral.me@gmail.com>
2023-03-29 06:51:58 +02:00
renovate[bot]
01b2826475 chore(deps): update google-github-actions/release-please-action digest to ee9822e (#132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 22:59:29 +02:00
Alex Jones
4f24368758 Merge pull request #131 from k8sgpt-ai/feat/security-policy
feat: security.md
2023-03-28 21:21:05 +01:00
Alex Jones
27b8916f29 feat: create-security.md 2023-03-28 20:20:08 +00:00
Alex Jones
fe73633273 feat: CODE_OF_CONDUCT.md (#129) 2023-03-28 21:33:50 +02:00
Alex Jones
a9d7bd42c2 Merge pull request #120 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.2
2023-03-28 20:26:23 +01:00
github-actions[bot]
2d481b8563 chore(main): release 0.1.2 2023-03-28 19:11:11 +00:00
Alex Jones
b78ab3d9b5 feat: added namespace filter (#127)
* feat: added namespace filter

Signed-off-by: AlexsJones <alexsimonjones@gmail.com>

* chore: Moved the explain bool out

Signed-off-by: AlexsJones <alexsimonjones@gmail.com>

---------

Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-03-28 21:10:42 +02:00
Thomas Schuetz
c8b92aaa0e fix: readme code blocks (#126)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 20:34:22 +02:00
Thomas Schuetz
65a568e937 feat: prefix templates (#125)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 20:30:59 +02:00
Thomas Schuetz
cb4932c39d docs: rename ISSUE_TEMPLATE (#124)
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 20:27:42 +02:00
Alex Jones
323a8b23ec Merge pull request #122 from k8sgpt-ai/doc/issue_templates
docs: add new issue templates
2023-03-28 19:22:19 +01:00
Alex Jones
be5fb62bb7 Merge branch 'main' into doc/issue_templates 2023-03-28 19:22:12 +01:00
Alex Jones
e912a8ded0 Merge pull request #123 from k8sgpt-ai/doc/fix_readme
docs: add wsl gcc instructions
2023-03-28 19:21:59 +01:00
Thomas Schuetz
a46416dce0 docs: fix indentations
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 19:41:50 +02:00
Thomas Schuetz
4d5566b4df docs: add WSL gcc instructions
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 19:40:59 +02:00
Thomas Schuetz
dbd305f901 docs: add new issue templates
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-03-28 19:22:54 +02:00
Sudipto Baral
11c227b82e chore: added default issue template (#96) (#121)
* chore: added default issue template (#96)

Signed-off-by: Sudipto Baral <sudiptobaral.me@gmail.com>

* chore: added addtional instruction through comment (#96)

Signed-off-by: Sudipto Baral <sudiptobaral.me@gmail.com>

---------

Signed-off-by: Sudipto Baral <sudiptobaral.me@gmail.com>
2023-03-28 18:55:19 +02:00
Alex Jones
05abe975dd fix: update README.md (#119) 2023-03-28 15:50:02 +02:00
Sai Kumar Peddireddy
3bfb278f81 docs: added Windows and Linux instalation steps in README (#116) 2023-03-28 15:45:48 +02:00
Alex Jones
b4865ff233 Merge pull request #106 from k8sgpt-ai/release-please--branches--main
chore(main): release 0.1.1
2023-03-28 14:05:31 +01:00
github-actions[bot]
95dfc62c4d chore(main): release 0.1.1 2023-03-28 13:02:54 +00:00
Alex Jones
b3efdf8525 Merge pull request #118 from k8sgpt-ai/feat/exhaustion
fix: short term solution for exhaustion
2023-03-28 14:02:21 +01:00
AlexsJones
5890e3a79c fix: short term solution for exhaustion
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-03-28 14:00:50 +01:00
Alex Jones
d0e4684e82 Merge pull request #113 from k8sgpt-ai/fix/service-failure
fix: this stops service exiting the program
2023-03-28 13:02:00 +01:00
AlexsJones
6f90386fc9 feat: this stops service exiting the program
Signed-off-by: AlexsJones <alexsimonjones@gmail.com>
2023-03-28 12:56:28 +01:00
Alex Jones
2f699a9929 Merge pull request #111 from k8sgpt-ai/chore/readme-patch
chore: update README.md
2023-03-28 12:26:14 +01:00
Alex Jones
93b947f261 chore: update README.md 2023-03-28 12:22:39 +01:00
Alex Jones
54143ea0ee Merge pull request #109 from k8sgpt-ai/chore/AlexsJones-patch-1-1
chore: update root.go path
2023-03-28 12:19:40 +01:00
Alex Jones
2cb1c9c150 chore: update root.go path 2023-03-28 12:18:48 +01:00
Alex Jones
02f5f921e2 Merge pull request #104 from k8sgpt-ai/chore/readme-update
feat: updated readme
2023-03-28 12:05:05 +01:00
Alex Jones
e0141d1cf5 feat: updated readme
Signed-off-by: Alex Jones <alexsimonjones@gmail.com>
2023-03-28 11:51:50 +01:00
91 changed files with 8567 additions and 752 deletions

2
.github/CODEOWNERS vendored
View File

@@ -9,4 +9,4 @@
# Unless a later match takes precedence, these owners will be requested for
# review when someone opens a pull request.
* @k8sgpt-ai/maintainers
* @k8sgpt-ai/maintainers @k8sgpt-ai/k8sgpt-maintainers @k8sgpt-ai/k8sgpt-approvers

View File

@@ -0,0 +1,35 @@
---
name: Feature Request
about: Suggest an idea for this project
title: 'feature: '
labels: 'type:question'
assignees: '@k8sgpt-ai/maintainers'
---
<!--
Thank you for initiating this feature request 🤗
To ensure conciseness, kindly try to adhere to the following format.
-->
Checklist:
* [ ] I've searched for similar issues and couldn't find anything matching
* [ ] I've discussed this feature in the #k8sgpt slack channel
## Is this feature request related to a problem?
* [ ] Yes
* [ ] No
<!-- If yes, please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
## Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
## Benefits for the project and its users
<!-- Describe the benefits this feature will bring to the project and its users. -->
## Potential drawbacks
<!-- Describe any potential drawbacks this feature might bring to the project and its users. -->
## Additional context
<!-- Add any other context about your feature request here. If applicable, add drawings to help explain. -->

50
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,50 @@
---
name: Question / Bug Report
about: Create a report to help us improve
title: 'question: '
labels: 'type:question'
assignees: ''
---
<!--
Thank you for initiating this issue request 🤗
To ensure conciseness, kindly try to adhere to the following format.
-->
Checklist:
* [ ] I've searched for similar issues and couldn't find anything matching
* [ ] I've included steps to reproduce the bug.
* [ ] I've included the version of Kubernetes and k8sgpt.
### Subject of the issue
<!-- Describe your issue here. -->
### Your environment
<!-- Describe your environment here. -->
* Version of Kubernetes
<!-- kubectl version -->
* Host OS and its version / If windows, is it WSL?
* Version of k8sgpt
<!-- k8sgpt version -->
### Steps to reproduce
<!-- Tell us how to reproduce this issue. -->
* Step 1
* Step 2
### Expected behaviour
<!-- Tell us what should happen -->
### Actual behaviour
<!-- Tell us what happens instead -->
### Additional context / screenshots
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->

View File

@@ -33,7 +33,7 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: Extract branch name
id: extract_branch
@@ -70,7 +70,7 @@ jobs:
RELEASE_REGISTRY: "localhost:5000/k8sgpt"
steps:
- name: Check out code
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: Set up Docker Buildx
id: buildx
@@ -115,7 +115,7 @@ jobs:
contents: read # Needed for checking out the repository
steps:
- name: Check out code
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: Login to GitHub Container Registry
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2

View File

@@ -23,9 +23,9 @@ jobs:
# Release-please creates a PR that tracks all changes
steps:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- uses: google-github-actions/release-please-action@e0b9d1885d92e9a93d5ce8656de60e3b806e542c # v3
- uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3
id: release
with:
command: manifest
@@ -36,19 +36,20 @@ jobs:
if: needs.release-please.outputs.releases_created == 'true'
permissions:
contents: write
needs:
- release-please
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: '1.20'
- name: Download Syft
uses: anchore/sbom-action/download-syft@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4
with:
@@ -70,9 +71,10 @@ jobs:
id-token: write
env:
IMAGE_TAG: ghcr.io/k8sgpt-ai/k8sgpt:${{ needs.release-please.outputs.tag_name }}
IMAGE_NAME: k8sgpt
steps:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
with:
submodules: recursive
@@ -98,11 +100,11 @@ jobs:
${{ env.IMAGE_TAG }}
builder: ${{ steps.buildx.outputs.name }}
push: true
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
- name: Generate SBOM
uses: anchore/sbom-action@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
with:
image: ${{ env.IMAGE_TAG }}
artifact-name: sbom-${{ env.IMAGE_NAME }}

27
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Run tests
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
GO_VERSION: "~1.20"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: Set up Go
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Test
run: go test -v ./...

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
k8sgpt*
*.vscode
dist/
bin/

View File

@@ -16,6 +16,27 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}}
nfpms:
- file_name_template: '{{ .ProjectName }}_{{ .Arch }}'
homepage: https://k8sgpt.ai
description: >-
K8sGPT is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english. It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
license: "MIT"
formats:
- deb
- rpm
- apk
bindir: /usr/bin
section: utils
contents:
- src: ./LICENSE
dst: /usr/share/doc/nfpm/copyright
file_info:
mode: 0644
sboms:
- artifacts: archive
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of uname.

View File

@@ -1 +1 @@
{".":"0.1.0"}
{".":"0.2.5"}

View File

@@ -1,5 +1,405 @@
# Changelog
## [0.2.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.4...v0.2.5) (2023-04-25)
### Features
* add configuration interface to support customer providers ([84a3cc0](https://github.com/k8sgpt-ai/k8sgpt/commit/84a3cc05fb6e21b732ef777351b42db8045e1093))
* add k8sgpt grafana dashboard ([#316](https://github.com/k8sgpt-ai/k8sgpt/issues/316)) ([ff79982](https://github.com/k8sgpt-ai/k8sgpt/commit/ff799825cfe5856bb97c8f38d939ec36b19fa30a))
* add serve & integration to README ([a65ee7f](https://github.com/k8sgpt-ai/k8sgpt/commit/a65ee7fc0957c7ba9369bdbe12e648818ca3f841))
* add subproject group to CODEOWNERS ([#322](https://github.com/k8sgpt-ai/k8sgpt/issues/322)) ([2391603](https://github.com/k8sgpt-ai/k8sgpt/commit/2391603075e73b91d9988d40eecddfc3e0593405))
* allow to set a baseurl ([#310](https://github.com/k8sgpt-ai/k8sgpt/issues/310)) ([cf797a6](https://github.com/k8sgpt-ai/k8sgpt/commit/cf797a6eb67efba957704077b4b04ed3ee166c24))
* async calls ([#311](https://github.com/k8sgpt-ai/k8sgpt/issues/311)) ([c3cc413](https://github.com/k8sgpt-ai/k8sgpt/commit/c3cc413e7fc3b06b310779dfa3cb4863ea9f3ed2))
* modify error handling to return a list of errors to display to the user at the end of analysis without blocking it if an error is detected (e.g., version of an object is not available on user's cluster) ([fa087ff](https://github.com/k8sgpt-ai/k8sgpt/commit/fa087ff5593871d2a07d68f203dd91e66c57e40b))
* the overall optimization and architecture design of the makefile are made ([#317](https://github.com/k8sgpt-ai/k8sgpt/issues/317)) ([754bf91](https://github.com/k8sgpt-ai/k8sgpt/commit/754bf917e1ac524699d38fb2dc59bc5d858f6d80))
* update readme ([#314](https://github.com/k8sgpt-ai/k8sgpt/issues/314)) ([ddd830c](https://github.com/k8sgpt-ai/k8sgpt/commit/ddd830cc569278c157480c44a671c9be20c95b24))
* use OS conform path for storing cached results ([7eddb8f](https://github.com/k8sgpt-ai/k8sgpt/commit/7eddb8f4a6dc61d5f66fc1bf56c0e8cbf9370229)), closes [#323](https://github.com/k8sgpt-ai/k8sgpt/issues/323)
### Bug Fixes
* **deps:** update module github.com/aquasecurity/trivy-operator to v0.13.1 ([#321](https://github.com/k8sgpt-ai/k8sgpt/issues/321)) ([e7f74db](https://github.com/k8sgpt-ai/k8sgpt/commit/e7f74db6e556146b898437bb777c2b803d1bec4f))
* **deps:** update module github.com/prometheus/client_golang to v1.15.0 ([#303](https://github.com/k8sgpt-ai/k8sgpt/issues/303)) ([df2ed41](https://github.com/k8sgpt-ai/k8sgpt/commit/df2ed4185b5a33a18e6b144c85bec3902c14d209))
* **deps:** update module github.com/sashabaranov/go-openai to v1.9.0 ([#298](https://github.com/k8sgpt-ai/k8sgpt/issues/298)) ([0472c36](https://github.com/k8sgpt-ai/k8sgpt/commit/0472c363a4d8a90556bc744fbf513ad63281e38b))
### Other
* add serviceMonitor in sample yaml ([#304](https://github.com/k8sgpt-ai/k8sgpt/issues/304)) ([0a4ed0d](https://github.com/k8sgpt-ai/k8sgpt/commit/0a4ed0d907c22a924dd79e8945eb9d6d10cd9ce7))
* analyze Pod ReadinessProbe faliure ([3c7e0bb](https://github.com/k8sgpt-ai/k8sgpt/commit/3c7e0bba1d4cc8247d248756dcfef884bc406992))
* change license to Apache-2 ([#313](https://github.com/k8sgpt-ai/k8sgpt/issues/313)) ([d0f7a11](https://github.com/k8sgpt-ai/k8sgpt/commit/d0f7a1105fe7ed317785782d3af45c83766b7d80))
## [0.2.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.3...v0.2.4) (2023-04-18)
### Features
* improve HPA analyzer to check ScaleTargetRef resources ([#283](https://github.com/k8sgpt-ai/k8sgpt/issues/283)) ([7173203](https://github.com/k8sgpt-ai/k8sgpt/commit/71732037fa40071cef0c2bc143736019d75eac86))
* init logging middleware on server mode ([6742410](https://github.com/k8sgpt-ai/k8sgpt/commit/6742410025d5e99c60045bb314730799f0e1e5ce))
### Bug Fixes
* deployment/cronjob namespace filtering ([#290](https://github.com/k8sgpt-ai/k8sgpt/issues/290)) ([3d684a2](https://github.com/k8sgpt-ai/k8sgpt/commit/3d684a2af7a9e1821bdb8b1bd6e85867b800d3ee))
* ensure parent directories are created in EnsureDirExists function ([#293](https://github.com/k8sgpt-ai/k8sgpt/issues/293)) ([af8b350](https://github.com/k8sgpt-ai/k8sgpt/commit/af8b350520d1a187a199482dd338db0086118db8))
* resolve language toggle bug (issue [#294](https://github.com/k8sgpt-ai/k8sgpt/issues/294)) ([0313627](https://github.com/k8sgpt-ai/k8sgpt/commit/03136278486ba12e3352580b317b9e63fa3a80f0))
## [0.2.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.2...v0.2.3) (2023-04-16)
### Features
* add node analyzer ([#272](https://github.com/k8sgpt-ai/k8sgpt/issues/272)) ([6247a1c](https://github.com/k8sgpt-ai/k8sgpt/commit/6247a1c0f3c2ead6a59661afed06973c29e57eca))
* add output query param on serve mode & refactor output logic ([9642202](https://github.com/k8sgpt-ai/k8sgpt/commit/9642202ed1b09c06a687651b7818c2a4df8a0c06))
* add server metrics ([#273](https://github.com/k8sgpt-ai/k8sgpt/issues/273)) ([a3becc9](https://github.com/k8sgpt-ai/k8sgpt/commit/a3becc9906515d0567808fee9a4e322451d6dc3f))
* envs to initialise server ([0071e25](https://github.com/k8sgpt-ai/k8sgpt/commit/0071e25992fc86c3882c2066873a2b04b43fe476))
* rename server/main.go to server/server.go ([9121a98](https://github.com/k8sgpt-ai/k8sgpt/commit/9121a983e52fa15c07bcc3bb361df97b8085c24c))
* running in cluster ([842f08c](https://github.com/k8sgpt-ai/k8sgpt/commit/842f08c655fde66b6b628192490e50be2ac3dcef))
* running in cluster ([3988eb2](https://github.com/k8sgpt-ai/k8sgpt/commit/3988eb2fd0a7d29ffa7b7bbc59960ca91e50466e))
* switch config file to XDG conform location ([dee4355](https://github.com/k8sgpt-ai/k8sgpt/commit/dee435514d7f717e4eb63b15a9d9fdb0722330ac))
* wip blocked until we have envs ([fe2c08c](https://github.com/k8sgpt-ai/k8sgpt/commit/fe2c08cf72a6ca271d1b431be66653f1396f304d))
### Bug Fixes
* add new line after version cmd output ([92e7b3d](https://github.com/k8sgpt-ai/k8sgpt/commit/92e7b3d3fb00c33ac48230caac34f45729e2f6b2))
* **deps:** update module github.com/sashabaranov/go-openai to v1.8.0 ([#277](https://github.com/k8sgpt-ai/k8sgpt/issues/277)) ([51b1b35](https://github.com/k8sgpt-ai/k8sgpt/commit/51b1b352acd24ebdc4cf9d9121f25c90e8f76ba7))
* resolve issue with duplicated integration filters. ([960ba56](https://github.com/k8sgpt-ai/k8sgpt/commit/960ba568d0dcc2ace722dc5c9b7c846366a98070))
* use the aiProvider object when launching the server instead of the deprecated configuration keys ([e7076ed](https://github.com/k8sgpt-ai/k8sgpt/commit/e7076ed6093aa9609d8c884b7a03e295057aaa8e))
### Other
* updated ([f0a0c9a](https://github.com/k8sgpt-ai/k8sgpt/commit/f0a0c9aebf627d65b0192ba3d0786cefd81e1fef))
## [0.2.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.1...v0.2.2) (2023-04-14)
### Features
* add simple health endpoint ([26c0cb2](https://github.com/k8sgpt-ai/k8sgpt/commit/26c0cb2eed75695220007e6d6f7b492c2641a149))
* anoymization based on pr feedback ([19e1b94](https://github.com/k8sgpt-ai/k8sgpt/commit/19e1b94e7c9ce4092f1dabd659023a193b2c4a92))
* anoymization based on pr feedback ([fe52951](https://github.com/k8sgpt-ai/k8sgpt/commit/fe529510b68ac5fbd39c147c7719abe2e7d20894))
* check for auth only in case of --explain ([57790e5](https://github.com/k8sgpt-ai/k8sgpt/commit/57790e5bc7037f57a4f73248fe05cac192511470))
* first version of serve ([b2e8add](https://github.com/k8sgpt-ai/k8sgpt/commit/b2e8adda333fbd508f0f01f2afcabc57bf9948c2))
* unified cmd and api ([9157d4d](https://github.com/k8sgpt-ai/k8sgpt/commit/9157d4dd1312bf75b336beb0e097422b303d22f1))
* updated api ([adae2ef](https://github.com/k8sgpt-ai/k8sgpt/commit/adae2ef71d81431711c552159362336e496b21ee))
### Bug Fixes
* bool conversion ([336ec2a](https://github.com/k8sgpt-ai/k8sgpt/commit/336ec2a42693d0df325b95cbebd9545b19e27725))
* **deps:** update module helm.sh/helm/v3 to v3.11.3 ([4dd91ed](https://github.com/k8sgpt-ai/k8sgpt/commit/4dd91ed8263292476054bc70d3d6a3149f88f1b3))
* naming ([159b385](https://github.com/k8sgpt-ai/k8sgpt/commit/159b3851ec54e93a447b0f13aa4ceb7b8b8f62db))
* start message ([6b63027](https://github.com/k8sgpt-ai/k8sgpt/commit/6b630275eb64b799c50e3074cb22a3b41bb893de))
### Docs
* fix Slack link ([1dccaea](https://github.com/k8sgpt-ai/k8sgpt/commit/1dccaea3f4f96b2da52999eed5031f02a89c0b6e))
### Other
* added oidc ([bffad41](https://github.com/k8sgpt-ai/k8sgpt/commit/bffad41134d231b16f136a619174ff3bee61765a))
* additional analyzers ([23071fd](https://github.com/k8sgpt-ai/k8sgpt/commit/23071fd2e6b421f0f5fcd6e7e4985c6900e5405c))
* **deps:** bump github.com/docker/docker ([#268](https://github.com/k8sgpt-ai/k8sgpt/issues/268)) ([7d1e2ac](https://github.com/k8sgpt-ai/k8sgpt/commit/7d1e2acaf3eaf00929ff43b9373df6a4be100795))
* **deps:** update actions/checkout digest to 83b7061 ([cbe6f27](https://github.com/k8sgpt-ai/k8sgpt/commit/cbe6f27c05e82f55f41b648b01972ba2c43f1534))
* **deps:** update actions/checkout digest to 8e5e7e5 ([#266](https://github.com/k8sgpt-ai/k8sgpt/issues/266)) ([0af34a1](https://github.com/k8sgpt-ai/k8sgpt/commit/0af34a1a95502dc26d7e08bac896f691e4969090))
* **deps:** update module oras.land/oras-go to v1.2.3 ([#249](https://github.com/k8sgpt-ai/k8sgpt/issues/249)) ([13c9231](https://github.com/k8sgpt-ai/k8sgpt/commit/13c9231aafef3a259fd678a80063ad2e968d6e95))
* fixing up tests ([f9b25d9](https://github.com/k8sgpt-ai/k8sgpt/commit/f9b25d9e85a8faaf1aae59d7bedc4c0f3538181e))
* fixing up tests ([498d454](https://github.com/k8sgpt-ai/k8sgpt/commit/498d454c174c7d39da1ca63b2a201e797d7e5e1c))
* Merge branch 'main' into feat/additional-analyzers ([4d36248](https://github.com/k8sgpt-ai/k8sgpt/commit/4d3624830ff840f9ccf11d7da20953bdf4c7c7fc))
* removing field ([ddb51c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ddb51c7af470044a8514ed013b44cc135e4c0f10))
## [0.2.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.0...v0.2.1) (2023-04-12)
### Features
* add anonymization example to README ([8a60b57](https://github.com/k8sgpt-ai/k8sgpt/commit/8a60b579409c67f092156ba1adf1be22cce37b8c))
* add anonymization flag ([d2a84ea](https://github.com/k8sgpt-ai/k8sgpt/commit/d2a84ea2b5c800dd900aac3a48b1914bd9ddb917))
* add more details on anonymize flag ([b687473](https://github.com/k8sgpt-ai/k8sgpt/commit/b687473e6169406002b0ee8be6ebb9ce43b46495))
* add storage class names' check. ([c8ba7d6](https://github.com/k8sgpt-ai/k8sgpt/commit/c8ba7d62d2f1d262263d1dff8f980e91cdcd50e8))
* improve documentation ([6f08654](https://github.com/k8sgpt-ai/k8sgpt/commit/6f0865413fc2854450d217225199cec199972490))
* improve documentation & update hpa message ([11326c1](https://github.com/k8sgpt-ai/k8sgpt/commit/11326c1c5f307c718e8d1e56099537314ffedadd))
* improve security of the MaskString function ([08f2a89](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2a89e54a65544322814286977b2c05acce89d))
* initial impl of integration ([b0e5170](https://github.com/k8sgpt-ai/k8sgpt/commit/b0e517006e65ac2b4e2d4e2696531d4bbf62c34b))
* initial impl of integration ([61d6e52](https://github.com/k8sgpt-ai/k8sgpt/commit/61d6e524657272cf3a967c724f212677fcfe7d2b))
* integration ready for first review ([3682f5c](https://github.com/k8sgpt-ai/k8sgpt/commit/3682f5c7ebb9590e92162eed214a8127f71bcd81))
* introduce StatefulSet analyser. ([c041ce2](https://github.com/k8sgpt-ai/k8sgpt/commit/c041ce2bbb4ecbc6f5637207c9f3071eee022744))
* refactor integration to use Failure object ([c0afc0f](https://github.com/k8sgpt-ai/k8sgpt/commit/c0afc0f5c91cfa50b1f7af901800ff0a2b492d18))
* return errors if filter specified by flag does not exist. ([dd5824f](https://github.com/k8sgpt-ai/k8sgpt/commit/dd5824f4365b01e3c501d8b5cda914dff138e03d))
### Bug Fixes
* **deps:** update kubernetes packages to v0.27.0 ([7a97034](https://github.com/k8sgpt-ai/k8sgpt/commit/7a97034cf41cb265111c752ee3d54fd90524ef59))
* **deps:** update module github.com/sashabaranov/go-openai to v1.7.0 ([#227](https://github.com/k8sgpt-ai/k8sgpt/issues/227)) ([5f3a5a5](https://github.com/k8sgpt-ai/k8sgpt/commit/5f3a5a54a02967acce40f8b4e9dd3a154c83f58c))
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([fe261b3](https://github.com/k8sgpt-ai/k8sgpt/commit/fe261b375f4d7990906620f53ac26e792a34731b))
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([ab55f15](https://github.com/k8sgpt-ai/k8sgpt/commit/ab55f157ef026502d29eadf5ad83e917fe085a6c))
* improve ReplaceIfMatch regex ([fd936ce](https://github.com/k8sgpt-ai/k8sgpt/commit/fd936ceaf725d1c1ed1f53eaa2204455dcd1e2af))
* pdb test ([705d2a0](https://github.com/k8sgpt-ai/k8sgpt/commit/705d2a0dcebb63783782e06b6b775393daf1efb7))
* use hpa namespace instead analyzer namespace ([#230](https://github.com/k8sgpt-ai/k8sgpt/issues/230)) ([a582d44](https://github.com/k8sgpt-ai/k8sgpt/commit/a582d444c5c53f25d7172947c690b35cad2cc176))
### Docs
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([b45ff1a](https://github.com/k8sgpt-ai/k8sgpt/commit/b45ff1aa8ef447df2b74bb8c6225e2f3d7c5bd63))
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([ba01bd4](https://github.com/k8sgpt-ai/k8sgpt/commit/ba01bd4b6ecd64fbe249be54f20471afc6339208))
### Other
* add fakeai provider ([#218](https://github.com/k8sgpt-ai/k8sgpt/issues/218)) ([e449cb6](https://github.com/k8sgpt-ai/k8sgpt/commit/e449cb60230d440d5b8e00062db63de5d6d413bf))
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([db1388f](https://github.com/k8sgpt-ai/k8sgpt/commit/db1388fd20dcf21069adcecd2796f2e1231162c8))
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([992b107](https://github.com/k8sgpt-ai/k8sgpt/commit/992b107c2d906663bb22998004a0859bccd45c77))
* compiling successfully ([80ac51c](https://github.com/k8sgpt-ai/k8sgpt/commit/80ac51c804351226e1764e3e649ac56e22de3749))
* **deps:** update anchore/sbom-action action to v0.14.1 ([#228](https://github.com/k8sgpt-ai/k8sgpt/issues/228)) ([9423b53](https://github.com/k8sgpt-ai/k8sgpt/commit/9423b53c1dbae3d0762420a0bacbdace9a2c18c9))
* **deps:** update google-github-actions/release-please-action digest to c078ea3 ([a1d8012](https://github.com/k8sgpt-ai/k8sgpt/commit/a1d8012a5c748aee3f16621d6da9a0f0c8cba293))
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([55dda43](https://github.com/k8sgpt-ai/k8sgpt/commit/55dda432ab89c4917bd28fceabcbe5569c0bf530))
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([21dc61c](https://github.com/k8sgpt-ai/k8sgpt/commit/21dc61c04f4d772b5147b38a4d28e5dbddf5cdd8))
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([428c348](https://github.com/k8sgpt-ai/k8sgpt/commit/428c3485868a7be95ea6776694e30b36badf4b5c))
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([3845d47](https://github.com/k8sgpt-ai/k8sgpt/commit/3845d4747f4e0fc823d1bcf631d6ecdd5e4ccd03))
* Fixing broken tests ([c809af3](https://github.com/k8sgpt-ai/k8sgpt/commit/c809af3f47388599fda3a88a4638feae1dc90492))
* fixing filters ([258c69a](https://github.com/k8sgpt-ai/k8sgpt/commit/258c69a17c977867dfd0a7ad02727270b7c172e7))
* fixing filters ([4d20f70](https://github.com/k8sgpt-ai/k8sgpt/commit/4d20f70fb40ff326ceb279f699068ec4956a2f10))
* merged ([096321b](https://github.com/k8sgpt-ai/k8sgpt/commit/096321b31a6cf0d53b1861a3e4ad1efe84f697cc))
* updated analysis_test.go ([825e9a4](https://github.com/k8sgpt-ai/k8sgpt/commit/825e9a43bd3ab7aa3ea52f315993cd778ea039e3))
* updated link output ([1b7f4ce](https://github.com/k8sgpt-ai/k8sgpt/commit/1b7f4ce44a499e5389aec42fdee00bfa81ef0888))
* updating based on feedback ([5e5d4b6](https://github.com/k8sgpt-ai/k8sgpt/commit/5e5d4b6de160dc7533067e1c0d8403c3faac1a9f))
* weird new line after filter removed ([fabe01a](https://github.com/k8sgpt-ai/k8sgpt/commit/fabe01aa019f1db45ed2ff780f0d6d63297b230b))
## [0.2.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.8...v0.2.0) (2023-04-05)
### ⚠ BREAKING CHANGES
* The format of the configuration file has changed. Users must update their configuration files to use the new format.
### Features
* add support for new configuration format ([9b243cd](https://github.com/k8sgpt-ai/k8sgpt/commit/9b243cdcaab1742fca2516bc1ae5710505e0eb65))
* add tests for hpa analyzer ([5a59abb](https://github.com/k8sgpt-ai/k8sgpt/commit/5a59abb55d9e76f3095b903ea973138b1afdccf2))
* add tests for ingress analyzer && Use t.Fatalf to report a fatal error if RunAnalysis returns an unexpected error ([e27e940](https://github.com/k8sgpt-ai/k8sgpt/commit/e27e9409dcaad78eefd79659e91364617407ae59))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.6.1 ([#207](https://github.com/k8sgpt-ai/k8sgpt/issues/207)) ([eeac731](https://github.com/k8sgpt-ai/k8sgpt/commit/eeac731858999f6f462a7b6ccf210af603674b30))
* **deps:** update module github.com/spf13/cobra to v1.7.0 ([5d5e082](https://github.com/k8sgpt-ai/k8sgpt/commit/5d5e082f417905954be33b3a620efef674f2588d))
* **deps:** update module github.com/stretchr/testify to v1.8.2 ([f5e3ca0](https://github.com/k8sgpt-ai/k8sgpt/commit/f5e3ca0bcab9325145a2e1d8624f585ffee8e29f))
* **deps:** update module golang.org/x/term to v0.7.0 ([8ab3573](https://github.com/k8sgpt-ai/k8sgpt/commit/8ab3573e13a3e3ab98e6f0aa76e429117c888f7f))
* details in json output ([2f21002](https://github.com/k8sgpt-ai/k8sgpt/commit/2f2100289953af7820bbb01f2c980cf5492de079))
* fixed hpa tests after rebase ([a24d1f1](https://github.com/k8sgpt-ai/k8sgpt/commit/a24d1f1b304e9448e63c1b7fc283b4cc8bc639aa))
* regression on dynamic filters ([93bcc62](https://github.com/k8sgpt-ai/k8sgpt/commit/93bcc627ba64a9139e65290a8512e0a9b4bf1a69))
* Spelling ([ba4d701](https://github.com/k8sgpt-ai/k8sgpt/commit/ba4d7016814ce97353e98658d5bbcd692007e4a9))
### Docs
* add curl command and release-please annoations ([1849209](https://github.com/k8sgpt-ai/k8sgpt/commit/184920988f7da928cca7fae4a676e4ee5f13cad1))
* add guide to details block ([ddc120e](https://github.com/k8sgpt-ai/k8sgpt/commit/ddc120e7c2657385737d3490def28dbabdd2242d))
* add installation guide via packages ([8e4ce6a](https://github.com/k8sgpt-ai/k8sgpt/commit/8e4ce6a974813258fb9cbeabbcaa3b8f6966a748))
* minor change ([53c1330](https://github.com/k8sgpt-ai/k8sgpt/commit/53c13305383eb454fe45fefa3483cef4821d5d34))
* modify README ([fc47c58](https://github.com/k8sgpt-ai/k8sgpt/commit/fc47c58ae1c2b5511ebbe0ed35714e4ecbb4bb7a))
* modify README ([0f46ceb](https://github.com/k8sgpt-ai/k8sgpt/commit/0f46ceb4456a90e7e05aeff23d25d5775bbf9c2b))
### Other
* added initial tests for json output ([22e3166](https://github.com/k8sgpt-ai/k8sgpt/commit/22e31661bff27b28339898826a34ffdcfcff3583))
* analyzer and ai interfacing ([#200](https://github.com/k8sgpt-ai/k8sgpt/issues/200)) ([0195bfa](https://github.com/k8sgpt-ai/k8sgpt/commit/0195bfab30ab748b3bb7f1b8c8f0e988b99ee54d))
* **deps:** pin anchore/sbom-action action to 448520c ([#203](https://github.com/k8sgpt-ai/k8sgpt/issues/203)) ([9ff3fbc](https://github.com/k8sgpt-ai/k8sgpt/commit/9ff3fbc382ed78b55a7a1966ecdae186c03b2848))
* **deps:** update golang docker tag to v1.20.3 ([e9994b8](https://github.com/k8sgpt-ai/k8sgpt/commit/e9994b8d167d4f1d9c0d0dabf8385ff22cfd16a4))
* made json output prettier and improved output ([db40734](https://github.com/k8sgpt-ai/k8sgpt/commit/db40734a0db89850a2a685c9a7f5f5559875b7b3))
## [0.1.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.7...v0.1.8) (2023-04-03)
### Features
* add password flag for backend authentication ([#199](https://github.com/k8sgpt-ai/k8sgpt/issues/199)) ([075a940](https://github.com/k8sgpt-ai/k8sgpt/commit/075a940d2c9bdd8aa9162940ed46abad47d46998))
* adding shields to readme ([213ecd8](https://github.com/k8sgpt-ai/k8sgpt/commit/213ecd8e83933fabaa5d3d674c67958599dd72ce))
* adding unit testing and example ([35b838b](https://github.com/k8sgpt-ai/k8sgpt/commit/35b838bfafa248dbf3932c7a3ee708b1a1539f18))
* alias filter to filters ([dde4e83](https://github.com/k8sgpt-ai/k8sgpt/commit/dde4e833b0e87553dea4e5c1e17a14e303956bc1))
* analyzer ifacing ([426f562](https://github.com/k8sgpt-ai/k8sgpt/commit/426f562be83ed0e708a07b9e1900ac06fa017c27))
* service test ([44cc8f7](https://github.com/k8sgpt-ai/k8sgpt/commit/44cc8f7ad68d152ec577e57cab7d8d9ab9613378))
* test workflow ([5f30a4d](https://github.com/k8sgpt-ai/k8sgpt/commit/5f30a4ddf44ebff949bb0573f261667539a2dcfb))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.8 ([91fb065](https://github.com/k8sgpt-ai/k8sgpt/commit/91fb06530a21259da6e72c28342e743d2b481294))
### Other
* create linux packages ([#201](https://github.com/k8sgpt-ai/k8sgpt/issues/201)) ([67753be](https://github.com/k8sgpt-ai/k8sgpt/commit/67753be6f317c462ebe1d9a316f2b0c9684ca4e5))
* **deps:** pin dependencies ([#198](https://github.com/k8sgpt-ai/k8sgpt/issues/198)) ([f8291aa](https://github.com/k8sgpt-ai/k8sgpt/commit/f8291aab085209f9fee13a6c92c96076163e2e90))
## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)
### Features
* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))
* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))
* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))
### Bug Fixes
* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))
* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))
* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))
### Refactoring
* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)
### Other
* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))
* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))
* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))
* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))
* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))
* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))
### Docs
* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))
## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)
### Bug Fixes
* analysis detail not displayed when --explain ([869ba90](https://github.com/k8sgpt-ai/k8sgpt/commit/869ba909075a5543413fb6ae7fc79aa067c08da4))
## [0.1.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.4...v0.1.5) (2023-03-31)
### Features
* add & remove default filter(s) to analyze. ([32ddf66](https://github.com/k8sgpt-ai/k8sgpt/commit/32ddf6691ce083fd4283a1d5ac4b9f02e90df867))
* add filter command add "list" subcommand ([#159](https://github.com/k8sgpt-ai/k8sgpt/issues/159)) ([6e17c9e](https://github.com/k8sgpt-ai/k8sgpt/commit/6e17c9e285e3871bb8f694b734a8cd6fd02e60f0))
* check if filters does not empty on add & remove ([975813d](https://github.com/k8sgpt-ai/k8sgpt/commit/975813d3284719c877630ad20f90c6fe163283da))
* remove filter prefix on subcommand ([30faf84](https://github.com/k8sgpt-ai/k8sgpt/commit/30faf842541c0be6b6483f71f6cf04d5cafecef5))
* rework filters ([3ed545f](https://github.com/k8sgpt-ai/k8sgpt/commit/3ed545f33fb3ecb3827c03e8c89027c61386c44f))
* update filters add & remove to be more consistent ([9aa0e89](https://github.com/k8sgpt-ai/k8sgpt/commit/9aa0e8960ee340208b4749954c99867842ba58b9))
### Bug Fixes
* kubecontext flag has no effect ([a8bf451](https://github.com/k8sgpt-ai/k8sgpt/commit/a8bf45134ff3a72dc3e531d720f119790faff9d4))
* spelling on dupplicateFilters ([0a12448](https://github.com/k8sgpt-ai/k8sgpt/commit/0a124484a23789376258413e73628c7b1d7abded))
### Other
* renamed filter list file ([25f8dc3](https://github.com/k8sgpt-ai/k8sgpt/commit/25f8dc390cccd66965993f464351e671af11f8ac))
## [0.1.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.3...v0.1.4) (2023-03-30)
### Features
* add Ingress class validation ([#154](https://github.com/k8sgpt-ai/k8sgpt/issues/154)) ([b061566](https://github.com/k8sgpt-ai/k8sgpt/commit/b061566404ef80288ca29add2d401574109d44c0))
* output selected backend ([#153](https://github.com/k8sgpt-ai/k8sgpt/issues/153)) ([be061da](https://github.com/k8sgpt-ai/k8sgpt/commit/be061da5b65045938acd70ad2eb2d21b87d2d6bf))
### Bug Fixes
* now supports different kubeconfig and kubectx ([c8f3c94](https://github.com/k8sgpt-ai/k8sgpt/commit/c8f3c946b00c00cd185961a4fa777806da94014e))
### Refactoring
* removed sample flag ([0afd528](https://github.com/k8sgpt-ai/k8sgpt/commit/0afd52844b96579391f77698bf0555145b6d2be8))
## [0.1.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.2...v0.1.3) (2023-03-30)
### Features
* add secret validation to ingress analyzer ([#141](https://github.com/k8sgpt-ai/k8sgpt/issues/141)) ([86c7e81](https://github.com/k8sgpt-ai/k8sgpt/commit/86c7e81e18db02ebcbfe35d470682c982871375f))
* bugfix for output ([2eab0c5](https://github.com/k8sgpt-ai/k8sgpt/commit/2eab0c544fbb6026f6aea79b08d8f29c061acf2e))
* CODE_OF_CONDUCT.md ([#129](https://github.com/k8sgpt-ai/k8sgpt/issues/129)) ([fe73633](https://github.com/k8sgpt-ai/k8sgpt/commit/fe73633273c5c1f4188bca48471283535967d5aa))
* create-security.md ([27b8916](https://github.com/k8sgpt-ai/k8sgpt/commit/27b8916f297570907437686c6d958636fb249d50))
* improvement to analysis speed ([548039e](https://github.com/k8sgpt-ai/k8sgpt/commit/548039ebe62bb609c1aa288e5e49845850fd2dd8))
* init ingress analyzer ([#138](https://github.com/k8sgpt-ai/k8sgpt/issues/138)) ([fe683b7](https://github.com/k8sgpt-ai/k8sgpt/commit/fe683b71b84fe82459b0ffe366b4dcfa1c978cfe))
### Bug Fixes
* add Ingress in GetParent switch case ([14ba8d5](https://github.com/k8sgpt-ai/k8sgpt/commit/14ba8d555005f31fc2201cb8b61653093c19b8a7))
* bugfix for output ([#148](https://github.com/k8sgpt-ai/k8sgpt/issues/148)) ([172c2df](https://github.com/k8sgpt-ai/k8sgpt/commit/172c2df6c55f5fddbfec7f8526be5f2323d1b900))
* Change ObjectMeta value in Ingress analyser. ([bf49a51](https://github.com/k8sgpt-ai/k8sgpt/commit/bf49a51c62af450cff51a590547ef30989bd2e93))
* typo in description of the filter flag in analyze command ([#147](https://github.com/k8sgpt-ai/k8sgpt/issues/147)) ([f4765be](https://github.com/k8sgpt-ai/k8sgpt/commit/f4765bed1b1ad121a81b35878fdb866354b5e34a))
### Other
* **deps:** update google-github-actions/release-please-action digest to ee9822e ([#132](https://github.com/k8sgpt-ai/k8sgpt/issues/132)) ([01b2826](https://github.com/k8sgpt-ai/k8sgpt/commit/01b282647512a4eaebd42ab5847b5534de148d14))
### Docs
* add new slack link ([#134](https://github.com/k8sgpt-ai/k8sgpt/issues/134)) ([#135](https://github.com/k8sgpt-ai/k8sgpt/issues/135)) ([cad2b38](https://github.com/k8sgpt-ai/k8sgpt/commit/cad2b38d037658495024ec0166ebd3e936f65c2e))
## [0.1.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.1...v0.1.2) (2023-03-28)
### Features
* added namespace filter ([#127](https://github.com/k8sgpt-ai/k8sgpt/issues/127)) ([b78ab3d](https://github.com/k8sgpt-ai/k8sgpt/commit/b78ab3d9b503a256bf6ccf18276e20140ae17d1c))
* prefix templates ([#125](https://github.com/k8sgpt-ai/k8sgpt/issues/125)) ([65a568e](https://github.com/k8sgpt-ai/k8sgpt/commit/65a568e937a8fdacc179f5e8b1a021a0178c04f0))
### Bug Fixes
* readme code blocks ([#126](https://github.com/k8sgpt-ai/k8sgpt/issues/126)) ([c8b92aa](https://github.com/k8sgpt-ai/k8sgpt/commit/c8b92aaa0e2795aa8d65f84277c8adfe0f1d14e3))
* update README.md ([#119](https://github.com/k8sgpt-ai/k8sgpt/issues/119)) ([05abe97](https://github.com/k8sgpt-ai/k8sgpt/commit/05abe975dd859cd85096a1a7182f17b0437ad20f))
### Other
* added default issue template ([#96](https://github.com/k8sgpt-ai/k8sgpt/issues/96)) ([#121](https://github.com/k8sgpt-ai/k8sgpt/issues/121)) ([11c227b](https://github.com/k8sgpt-ai/k8sgpt/commit/11c227b82e16dac8b46cbd03bb04d9cc1c2b5ac3))
### Docs
* add new issue templates ([dbd305f](https://github.com/k8sgpt-ai/k8sgpt/commit/dbd305f901cca09b7148254c3aa7a7435504d6cc))
* add WSL gcc instructions ([4d5566b](https://github.com/k8sgpt-ai/k8sgpt/commit/4d5566b4df7aedf43edbeeb03130f0ba77dbed1a))
* added Windows and Linux instalation steps in README ([#116](https://github.com/k8sgpt-ai/k8sgpt/issues/116)) ([3bfb278](https://github.com/k8sgpt-ai/k8sgpt/commit/3bfb278f81a9c550ee37a88c0cb0377331802542))
* fix indentations ([a46416d](https://github.com/k8sgpt-ai/k8sgpt/commit/a46416dce0f5cee2d42b27525023b04af1a8e3c0))
* rename ISSUE_TEMPLATE ([#124](https://github.com/k8sgpt-ai/k8sgpt/issues/124)) ([cb4932c](https://github.com/k8sgpt-ai/k8sgpt/commit/cb4932c39df4903a4b48ae5f0428860027f76fd2))
## [0.1.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.0...v0.1.1) (2023-03-28)
### Features
* this stops service exiting the program ([6f90386](https://github.com/k8sgpt-ai/k8sgpt/commit/6f90386fc93b2e39e59832468922e8ba7210b8e5))
* updated readme ([e0141d1](https://github.com/k8sgpt-ai/k8sgpt/commit/e0141d1cf54b5b37b25a5caeb9d5c940b9410ea7))
### Bug Fixes
* short term solution for exhaustion ([5890e3a](https://github.com/k8sgpt-ai/k8sgpt/commit/5890e3a79c80a2973af2feb7d50e7f9c57c563c2))
### Other
* update README.md ([93b947f](https://github.com/k8sgpt-ai/k8sgpt/commit/93b947f261e401c10dde6dc1854e6e22187437d6))
* update root.go path ([2cb1c9c](https://github.com/k8sgpt-ai/k8sgpt/commit/2cb1c9c150d052bb3942d9f62ded9d54b0e1873e))
## [0.1.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.9...v0.1.0) (2023-03-28)

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
contact@k8sgpt.ai.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

215
LICENSE
View File

@@ -1,21 +1,202 @@
MIT License
Copyright (c) 2023 Alex Jones
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Definitions.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

147
Makefile Normal file
View File

@@ -0,0 +1,147 @@
# Copyright 2023 K8sgpt AI. All rights reserved.
# Use of this source code is governed by a MIT style
# license that can be found in the LICENSE file.
# ==============================================================================
# define the default goal
#
ROOT_PACKAGE=github.com/k8sgpt-ai/k8sgpt
SHELL := /bin/bash
DIRS=$(shell ls)
GO=go
.DEFAULT_GOAL := help
# include the common makefile
COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
# ROOT_DIR: root directory of the code base
ifeq ($(origin ROOT_DIR),undefined)
ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/. && pwd -P))
endif
# OUTPUT_DIR: The directory where the build output is stored.
ifeq ($(origin OUTPUT_DIR),undefined)
OUTPUT_DIR := $(ROOT_DIR)/bin
$(shell mkdir -p $(OUTPUT_DIR))
endif
ifeq ($(origin VERSION), undefined)
VERSION := $(shell git describe --abbrev=0 --dirty --always --tags | sed 's/-/./g')
endif
# Check if the tree is dirty. default to dirty(maybe u should commit?)
GIT_TREE_STATE:="dirty"
ifeq (, $(shell git status --porcelain 2>/dev/null))
GIT_TREE_STATE="clean"
endif
GIT_COMMIT:=$(shell git rev-parse HEAD)
IMG ?= ghcr.io/k8sgpt-ai/k8sgpt:latest
BUILDFILE = "./main.go"
BUILDAPP = "$(OUTPUT_DIR)/k8sgpt"
.PHONY: all
all: tidy add-copyright lint cover build
# ==============================================================================
# Targets
## build: Build binaries by default
.PHONY: build
build:
@echo "$(shell go version)"
@echo "===========> Building binary $(BUILDAPP) *[Git Info]: $(VERSION)-$(GIT_COMMIT)"
@export CGO_ENABLED=0 && go build -o $(BUILDAPP) -ldflags '-s -w' $(BUILDFILE)
## tidy: tidy go.mod
.PHONY: tidy
tidy:
@$(GO) mod tidy
## deploy: Deploy k8sgpt
.PHONY: deploy
deploy:
@echo "===========> Deploying k8sgpt"
@$(call funcsecret)
## undeploy: Undeploy k8sgpt
.PHONY: undeploy
undeploy:
@echo "===========> Undeploying k8sgpt"
kubectl delete secret ai-backend-secret --namespace=k8sgpt
kubectl delete -f container/manifests
kubectl delete ns k8sgpt
## docker-build: Build docker image
.PHONY: docker-build
docker-build:
@echo "===========> Building docker image"
docker buildx build --build-arg=VERSION="$$(git describe --tags --abbrev=0)" --build-arg=COMMIT="$$(git rev-parse --short HEAD)" --build-arg DATE="$$(date +%FT%TZ)" --platform="linux/amd64,linux/arm64" -t ${IMG} -f container/Dockerfile . --push
## fmt: Run go fmt against code.
.PHONY: fmt
fmt:
@$(GO) fmt ./...
## vet: Run go vet against code.
.PHONY: vet
vet:
@$(GO) vet ./...
## lint: Run go lint against code.
.PHONY: lint
lint:
@golangci-lint run -v ./...
## style: Code style -> fmt,vet,lint
.PHONY: style
style: fmt vet lint
## test: Run unit test
.PHONY: test
test:
@echo "===========> Run unit test"
@$(GO) test ./...
## cover: Run unit test with coverage
.PHONY: cover
cover: test
@$(GO) test -cover
## go.clean: Clean all builds
.PHONY: clean
clean:
@echo "===========> Cleaning all builds OUTPUT_DIR($(OUTPUT_DIR))"
@-rm -vrf $(OUTPUT_DIR)
@echo "===========> End clean..."
## help: Show this help info.
.PHONY: help
help: Makefile
@printf "\n\033[1mUsage: make <TARGETS> ...\033[0m\n\n\\033[1mTargets:\\033[0m\n\n"
@sed -n 's/^##//p' $< | awk -F':' '{printf "\033[36m%-28s\033[0m %s\n", $$1, $$2}' | sed -e 's/^/ /'
## copyright.verify: Validate boilerplate headers for assign files
.PHONY: copyright.verify
copyright.verify: tools.verify.addlicense
@echo "===========> Validate boilerplate headers for assign files starting in the $(ROOT_DIR) directory"
# @addlicense -v -check -ignore **/test/** -f $(LICENSE_TEMPLATE) $(CODE_DIRS)
@echo "===========> End of boilerplate headers check..."
## copyright.add: Add the boilerplate headers for all files
.PHONY: copyright.add
copyright.add: tools.verify.addlicense
@echo "===========> Adding $(LICENSE_TEMPLATE) the boilerplate headers for all files"
# @addlicense -y $(shell date +"%Y") -v -c "K8sgpt AI." -f $(LICENSE_TEMPLATE) $(CODE_DIRS)
@echo "===========> End the copyright is added..."
define funcsecret
ifndef SECRET
$(error SECRET environment variable is not set)
endif
kubectl create ns k8sgpt || true
kubectl create secret generic ai-backend-secret --from-literal=secret-key=$(SECRET) --namespace=k8sgpt || true
kubectl apply -f container/manifests
endef

288
README.md
View File

@@ -2,21 +2,126 @@
<source media="(prefers-color-scheme: dark)" srcset="./images/banner-white.png" width="600px;">
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
</picture>
<br/>
`k8sgpt` is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english.
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/k8sgpt-ai/k8sgpt)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/k8sgpt-ai/k8sgpt/release.yaml)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/k8sgpt-ai/k8sgpt)
It has SRE experience codified into it's analyzers and helps to pull out the most relevent information to enrich it with AI.
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
## Quick Start
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
<a href="https://www.producthunt.com/posts/k8sgpt?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-k8sgpt" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=389489&theme=light" alt="K8sGPT - K8sGPT&#0032;gives&#0032;Kubernetes&#0032;Superpowers&#0032;to&#0032;everyone | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
# Installation
## Linux/Mac via brew
```
brew tap k8sgpt-ai/k8sgpt
brew install k8sgpt
```
<details>
<summary>RPM-based installation (RedHat/CentOS/Fedora)</summary>
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_386.rpm
sudo rpm -ivh k8sgpt_386.rpm
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_amd64.rpm
sudo rpm -ivh -i k8sgpt_amd64.rpm
```
<!---x-release-please-end-->
</details>
<details>
<summary>DEB-based installation (Ubuntu/Debian)</summary>
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_386.deb
sudo dpkg -i k8sgpt_386.deb
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_amd64.deb
sudo dpkg -i k8sgpt_amd64.deb
```
<!---x-release-please-end-->
</details>
<details>
<summary>APK-based installation (Alpine)</summary>
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_386.apk
apk add k8sgpt_386.apk
```
<!---x-release-please-end-->
**64 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.5/k8sgpt_amd64.apk
apk add k8sgpt_amd64.apk
```
<!---x-release-please-end-->x
</details>
<details>
<summary>Failing Installation on WSL or Linux (missing gcc)</summary>
When installing Homebrew on WSL or Linux, you may encounter the following error:
```
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be
built from the source. k8sgpt Install Clang or run brew install gcc.
```
If you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.
```
sudo apt-get update
sudo apt-get install build-essential
```
</details>
## Windows
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
tab based on your system architecture.
* Extract the downloaded package to your desired location. Configure the system *path* variable with the binary location
## Verify installation
* Run `k8sgpt version`
<hr>
## Quick Start
* Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)
* You can do this by running `k8sgpt generate` to open a browser link to generate it
* You can do this by running `k8sgpt generate` to open a browser link to generate it
* Run `k8sgpt auth` to set it in k8sgpt.
* You can provide the password directly using the `--password` flag.
* Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis.
* Run `k8sgpt analyze` to run a scan.
* And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.
@@ -24,19 +129,35 @@ brew install k8sgpt
## Analyzers
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but you will be able to write your own analyzers.
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
you will be able to write your own analyzers.
### Built in analyzers
#### Enabled by default
- [x] podAnalyzer
- [x] pvcAnalyzer
- [x] rsAnalyzer
- [x] serviceAnalyzer
- [x] eventAnalyzer
- [x] ingressAnalyzer
- [x] statefulSetAnalyzer
- [x] deploymentAnalyzer
- [x] cronJobAnalyzer
- [x] nodeAnalyzer
#### Optional
- [x] hpaAnalyzer
- [x] pdbAnalyzer
- [x] networkPolicyAnalyzer
## Usage
```
Kubernetes debugging powered by AI
Usage:
k8sgpt [command]
@@ -44,20 +165,52 @@ Available Commands:
analyze This command will find problems within your Kubernetes cluster
auth Authenticate with your chosen backend
completion Generate the autocompletion script for the specified shell
filters Manage filters for analyzing Kubernetes resources
generate Generate Key for your chosen backend (opens browser)
help Help about any command
integration Intergrate another tool into K8sGPT
serve Runs k8sgpt as a server
version Print the version number of k8sgpt
Flags:
--config string config file (default is $HOME/.k8sgpt.git.yaml)
-h, --help help for k8sgpt
--kubeconfig string Path to a kubeconfig. Only required if out-of-cluster.
--master string The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
-t, --toggle Help message for toggle
--config string config file (default is $HOME/.k8sgpt.yaml)
-h, --help help for k8sgpt
--kubeconfig string Path to a kubeconfig. Only required if out-of-cluster. (default "$HOME/.kube/config")
--kubecontext string Kubernetes context to use. Only required if out-of-cluster.
Use "k8sgpt [command] --help" for more information about a command.
```
_Manage filters_
_List filters_
```
k8sgpt filters list
```
_Add default filters_
```
k8sgpt filters add [filter(s)]
```
### Examples :
- Simple filter : `k8sgpt filters add Service`
- Multiple filters : `k8sgpt filters add Ingress,Pod`
_Add default filters_
```
k8sgpt filters remove [filter(s)]
```
### Examples :
- Simple filter : `k8sgpt filters remove Service`
- Multiple filters : `k8sgpt filters remove Ingress,Pod`
_Run a scan with the default analyzers_
```
@@ -69,34 +222,117 @@ k8sgpt analyze --explain
_Filter on resource_
```
k8sgpt analyze --explain --resource=Service
k8sgpt analyze --explain --filter=Service
```
_Filter by namespace_
```
k8sgpt analyze --explain --filter=Pod --namespace=default
```
_Output to JSON_
```
k8sgpt analyze --explain --resource=Service --output=json
k8sgpt analyze --explain --filter=Service --output=json
```
## Upcoming major milestones
_Anonymize during explain_
- [ ] Multiple AI backend support
- [ ] Custom AI/ML model backend support
- [ ] Custom analyzers
```
k8sgpt analyze --explain --filter=Service --output=json --anonymize
```
### What about kubectl-ai?
### How does anonymization work?
The the kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the cluster. It is not what we are trying to do here, it is focusing on writing YAML manifests.
With this option, the data is anonymized before being sent to the AI Backend. During the analysis execution, `k8sgpt` retrieves sensitive data (Kubernetes object names, labels, etc.). This data is masked when sent to the AI backend and replaced by a key that can be used to de-anonymize the data when the solution is returned to the user.
K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find the root cause of an issue.
<details>
1. Error reported during analysis:
```bash
Error: HorizontalPodAutoscaler uses StatefulSet/fake-deployment as ScaleTargetRef which does not exist.
```
2. Payload sent to the AI backend:
```bash
Error: HorizontalPodAutoscaler uses StatefulSet/tGLcCRcHa1Ce5Rs as ScaleTargetRef which does not exist.
```
3. Payload returned by the AI:
```bash
The Kubernetes system is trying to scale a StatefulSet named tGLcCRcHa1Ce5Rs using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.
```
4. Payload returned to the user:
```bash
The Kubernetes system is trying to scale a StatefulSet named fake-deployment using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.
```
**Anonymization does not currently apply to events.**
</details>
### Additional commands
<details>
_Manage integrations_
_List integrations_
```
k8sgpt integrations list
```
_Activate integrations_
```
k8sgpt integrations activate [integration(s)]
```
_Use integration_
```
k8sgpt analyze --filter=[integration(s)]
```
_Deactivate integrations_
```
k8sgpt integrations deactivate [integration(s)]
```
_Serve mode_
```
k8sgpt serve
```
_Analysis with serve mode_
```
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
```
</details>
## Configuration
`k8sgpt` stores config data in the `$XDG_CONFIG_HOME/k8sgpt/k8sgpt.yaml` file. The data is stored in plain text, including your OpenAI key.
Config file locations:
| OS | Path |
|---------|--------------------------------------------------|
| MacOS | ~/Library/Application Support/k8sgpt/k8sgpt.yaml |
| Linux | ~/.config/k8sgpt/k8sgpt.yaml |
| Windows | %LOCALAPPDATA%/k8sgpt/k8sgpt.yaml |
### Configuration
`k8sgpt` stores config data in `~/.k8sgpt` the data is stored in plain text, including your OpenAI key.
### Contributing
## Contributing
Please read our [contributing guide](./CONTRIBUTING.md).
### Community
* Find us on [Slack](https://cloud-native.slack.com/channels/k8sgpt-ai)
## Community
Find us on [Slack](https://join.slack.com/t/k8sgpt/shared_invite/zt-1rwe5fpzq-VNtJK8DmYbbm~iWL1H34nw)
<a href="https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt" />
</a>

11
SECURITY.md Normal file
View File

@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
We currently support the latest release for security patching and will deploy forward releases.
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`
## Reporting a Vulnerability
If you are aware of a vulnverability please feel free to disclose it responsibly to contact@k8sgpt.ai or to one of our maintainers in our Slack community.

View File

@@ -1,28 +1,37 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyze
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/schollz/progressbar/v3"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
explain bool
backend string
output string
filters []string
language string
nocache bool
explain bool
backend string
output string
filters []string
language string
nocache bool
namespace string
anonymize bool
maxConcurrency int
)
// AnalyzeCmd represents the problems command
@@ -34,112 +43,50 @@ var AnalyzeCmd = &cobra.Command{
provide you with a list of issues that need to be resolved`,
Run: func(cmd *cobra.Command, args []string) {
// get backend from file
backendType := viper.GetString("backend_type")
if backendType == "" {
color.Red("No backend set. Please run k8sgpt auth")
os.Exit(1)
}
// override the default backend if a flag is provided
if backend != "" {
backendType = backend
}
// get the token with viper
token := viper.GetString(fmt.Sprintf("%s_key", backendType))
// check if nil
if token == "" {
color.Red("No %s key set. Please run k8sgpt auth", backendType)
os.Exit(1)
}
var aiClient ai.IAI
switch backendType {
case "openai":
aiClient = &ai.OpenAIClient{}
if err := aiClient.Configure(token, language); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
default:
color.Red("Backend not supported")
os.Exit(1)
}
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{}
if err := analyzer.RunAnalysis(ctx, client, aiClient, explain, analysisResults); err != nil {
// AnalysisResult configuration
config, err := analysis.NewAnalysis(backend,
language, filters, namespace, nocache, explain, maxConcurrency)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Removed filtered results from slice
if len(filters) > 0 {
var filteredResults []analyzer.Analysis
for _, analysis := range *analysisResults {
for _, filter := range filters {
if strings.Contains(analysis.Kind, filter) {
filteredResults = append(filteredResults, analysis)
}
}
analysisErrors := config.RunAnalysis()
if len(analysisErrors) != 0 {
for _, err := range analysisErrors {
color.Red("Error: %s", err)
}
analysisResults = &filteredResults
}
var bar *progressbar.ProgressBar
if len(*analysisResults) > 0 {
bar = progressbar.Default(int64(len(*analysisResults)))
} else {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
}
// This variable is used to store the results that will be printed
// It's necessary because the heap memory is lost when the function returns
var printOutput []analyzer.Analysis
for _, analysis := range *analysisResults {
if explain {
parsedText, err := analyzer.ParseViaAI(ctx, aiClient, analysis.Error, nocache)
if err != nil {
color.Red("Error: %v", err)
continue
}
analysis.Details = parsedText
bar.Add(1)
if explain {
err := config.GetAIResults(output, anonymize)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
printOutput = append(printOutput, analysis)
}
// print results
for n, analysis := range printOutput {
switch output {
case "json":
analysis.Error = analysis.Error[0:]
j, err := json.Marshal(analysis)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(j))
default:
fmt.Printf("%s %s(%s): %s \n%s\n", color.CyanString("%d", n),
color.YellowString(analysis.Name), color.CyanString(analysis.ParentObject),
color.RedString(analysis.Error[0]), color.GreenString(analysis.Details))
}
output, err := config.PrintOutput(output)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(output))
},
}
func init() {
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "n", false, "Do not use cached data")
// namespace flag
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
// no cache flag
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "c", false, "Do not use cached data")
// anonymize flag
AnalyzeCmd.Flags().BoolVarP(&anonymize, "anonymize", "a", false, "Anonymize data before sending it to the AI backend. This flag masks sensitive data, such as Kubernetes object names and labels, by replacing it with a key. However, please note that this flag does not currently apply to events.")
// array of strings flag
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analzyers (e.g. Pod,PersistentVolumeClaim,Service,ReplicaSet)")
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)")
// explain flag
AnalyzeCmd.Flags().BoolVarP(&explain, "explain", "e", false, "Explain the problem to me")
// add flag for backend
AnalyzeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
@@ -147,4 +94,6 @@ func init() {
AnalyzeCmd.Flags().StringVarP(&output, "output", "o", "text", "Output format (text, json)")
// add language options for output
AnalyzeCmd.Flags().StringVarP(&language, "language", "l", "english", "Languages to use for AI (e.g. 'English', 'Spanish', 'French', 'German', 'Italian', 'Portuguese', 'Dutch', 'Russian', 'Chinese', 'Japanese', 'Korean')")
// add max concurrency
AnalyzeCmd.Flags().IntVarP(&maxConcurrency, "max-concurrency", "m", 10, "Maximum number of concurrent requests to the Kubernetes API server")
}

View File

@@ -1,3 +1,16 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
@@ -7,13 +20,17 @@ import (
"syscall"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
)
var (
backend string
backend string
password string
baseURL string
model string
)
// authCmd represents the auth command
@@ -23,30 +40,66 @@ var AuthCmd = &cobra.Command{
Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
Run: func(cmd *cobra.Command, args []string) {
backendType := viper.GetString("backend_type")
if backendType == "" {
// Set the default backend
viper.Set("backend_type", "openai")
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
}
// override the default backend if a flag is provided
if backend != "" {
backendType = backend
}
fmt.Printf("Enter %s Key: ", backendType)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
// get ai configuration
var configAI ai.AIConfiguration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backendType,
err.Error())
color.Red("Error: %v", err)
os.Exit(1)
}
password := strings.TrimSpace(string(bytePassword))
viper.Set(fmt.Sprintf("%s_key", backendType), password)
// search for provider with same name
providerIndex := -1
for i, provider := range configAI.Providers {
if backend == provider.Name {
providerIndex = i
break
}
}
// check if backend is not empty
if backend == "" {
color.Red("Error: Backend AI cannot be empty.")
os.Exit(1)
}
color.Green("Using %s as backend AI provider", backend)
// check if model is not empty
if model == "" {
color.Red("Error: Model cannot be empty.")
os.Exit(1)
}
if password == "" {
fmt.Printf("Enter %s Key: ", backend)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backend,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}
// create new provider object
newProvider := ai.AIProvider{
Name: backend,
Model: model,
Password: password,
BaseURL: baseURL,
}
if providerIndex == -1 {
// provider with same name does not exist, add new provider to list
configAI.Providers = append(configAI.Providers, newProvider)
color.Green("New provider added")
} else {
// provider with same name exists, update provider info
configAI.Providers[providerIndex] = newProvider
color.Green("Provider updated")
}
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
@@ -58,4 +111,10 @@ var AuthCmd = &cobra.Command{
func init() {
// add flag for backend
AuthCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
// add flag for model
AuthCmd.Flags().StringVarP(&model, "model", "m", "gpt-3.5-turbo", "Backend AI model")
// add flag for password
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
// add flag for url
AuthCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
}

85
cmd/filters/add.go Normal file
View File

@@ -0,0 +1,85 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package filters
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var addCmd = &cobra.Command{
Use: "add [filter(s)]",
Short: "Adds one or more new filters.",
Long: `The add command adds one or more new filters to the default set of filters used by the analyze.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
// Verify filter exist
invalidFilters := []string{}
for _, f := range inputFilters {
if f == "" {
color.Red("Filter cannot be empty. Please use correct syntax.")
os.Exit(1)
}
foundFilter := false
for _, filter := range availableFilters {
if filter == f {
foundFilter = true
break
}
}
if !foundFilter {
invalidFilters = append(invalidFilters, f)
}
}
if len(invalidFilters) != 0 {
color.Red("Filter %s does not exist. Please use k8sgpt filters list", strings.Join(invalidFilters, ", "))
os.Exit(1)
}
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
mergedFilters := append(activeFilters, inputFilters...)
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
// Verify dupplicate
if len(dupplicatedFilters) != 0 {
color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
os.Exit(1)
}
viper.Set("active_filters", uniqueFilters)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("Filter %s added", strings.Join(inputFilters, ", "))
},
}

38
cmd/filters/filters.go Normal file
View File

@@ -0,0 +1,38 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package filters
import (
"github.com/spf13/cobra"
)
var FiltersCmd = &cobra.Command{
Use: "filters",
Aliases: []string{"filter"},
Short: "Manage filters for analyzing Kubernetes resources",
Long: `The filters command allows you to manage filters that are used to analyze Kubernetes resources.
You can list available filters to analyze resources.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help()
return
}
},
}
func init() {
FiltersCmd.AddCommand(listCmd)
FiltersCmd.AddCommand(addCmd)
FiltersCmd.AddCommand(removeCmd)
}

65
cmd/filters/list.go Normal file
View File

@@ -0,0 +1,65 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package filters
import (
"fmt"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List available filters",
Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
Run: func(cmd *cobra.Command, args []string) {
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
fmt.Printf(color.YellowString("Active: \n"))
for _, filter := range activeFilters {
// if the filter is an integration, mark this differently
if util.SliceContainsString(integrationFilters, filter) {
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
} else {
fmt.Printf("> %s\n", color.GreenString(filter))
}
}
// display inactive filters
if len(inactiveFilters) != 0 {
fmt.Printf(color.YellowString("Unused: \n"))
for _, filter := range inactiveFilters {
// if the filter is an integration, mark this differently
if util.SliceContainsString(integrationFilters, filter) {
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
} else {
fmt.Printf("> %s\n", color.RedString(filter))
}
}
}
},
}

87
cmd/filters/remove.go Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package filters
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var removeCmd = &cobra.Command{
Use: "remove [filter(s)]",
Short: "Remove one or more filters.",
Long: `The add command remove one or more filters to the default set of filters used by the analyze.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, _, _ := analyzer.ListFilters()
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
// Check if input input filters is not empty
for _, f := range inputFilters {
if f == "" {
color.Red("Filter cannot be empty. Please use correct syntax.")
os.Exit(1)
}
}
// verify dupplicate filters example: k8sgpt filters remove Pod Pod
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(inputFilters)
if len(dupplicatedFilters) != 0 {
color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
os.Exit(1)
}
// Verify if filter exist in config file and update default_filter
filterNotFound := []string{}
for _, filter := range uniqueFilters {
foundFilter := false
for i, f := range activeFilters {
if f == filter {
foundFilter = true
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
break
}
}
if !foundFilter {
filterNotFound = append(filterNotFound, filter)
}
}
if len(filterNotFound) != 0 {
color.Red("Filter(s) %s does not exist in configuration file. Please use k8sgpt filters add.", strings.Join(filterNotFound, ", "))
os.Exit(1)
}
viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("Filter(s) %s removed", strings.Join(inputFilters, ", "))
},
}

View File

@@ -1,3 +1,16 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package generate
import (
@@ -7,7 +20,6 @@ import (
"github.com/spf13/viper"
"os/exec"
"runtime"
"time"
)
var (
@@ -31,10 +43,6 @@ var GenerateCmd = &cobra.Command{
backendType = backend
}
fmt.Println("")
color.Green("Opening: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
fmt.Println("")
time.Sleep(5 * time.Second)
openbrowser("https://beta.openai.com/account/api-keys")
},
}
@@ -46,9 +54,15 @@ func init() {
func openbrowser(url string) {
var err error
isGui := true
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
_, err = exec.LookPath("xdg-open")
if err != nil {
isGui = false
} else {
err = exec.Command("xdg-open", url).Start()
}
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
@@ -56,7 +70,21 @@ func openbrowser(url string) {
default:
err = fmt.Errorf("unsupported platform")
}
printInstructions(isGui, backend)
if err != nil {
fmt.Println(err)
}
}
func printInstructions(isGui bool, backendType string) {
fmt.Println("")
if isGui {
color.Green("Opening: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
fmt.Println("")
} else {
color.Green("Please open: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
fmt.Println("")
}
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
fmt.Println("")
}

View File

@@ -0,0 +1,46 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package integration
import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// activateCmd represents the activate command
var activateCmd = &cobra.Command{
Use: "activate [integration]",
Short: "Activate an integration",
Long: ``,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
integrationName := args[0]
integration := integration.NewIntegration()
// Check if the integation exists
err := integration.Activate(integrationName, namespace)
if err != nil {
color.Red("Error: %v", err)
return
}
color.Green("Activated integration %s", integrationName)
},
}
func init() {
IntegrationCmd.AddCommand(activateCmd)
}

View File

@@ -0,0 +1,45 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package integration
import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// deactivateCmd represents the deactivate command
var deactivateCmd = &cobra.Command{
Use: "deactivate [integration]",
Short: "Deactivate an integration",
Args: cobra.ExactArgs(1),
Long: `For example e.g. k8sgpt integration deactivate trivy`,
Run: func(cmd *cobra.Command, args []string) {
integrationName := args[0]
integration := integration.NewIntegration()
if err := integration.Deactivate(integrationName, namespace); err != nil {
color.Red("Error: %v", err)
return
}
color.Green("Deactivated integration %s", integrationName)
},
}
func init() {
IntegrationCmd.AddCommand(deactivateCmd)
}

View File

@@ -0,0 +1,41 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package integration
import (
"github.com/spf13/cobra"
)
var (
namespace string
)
// IntegrationCmd represents the integrate command
var IntegrationCmd = &cobra.Command{
Use: "integration",
Aliases: []string{"integrations"},
Short: "Intergrate another tool into K8sGPT",
Long: `Intergrate another tool into K8sGPT. For example:
k8sgpt integration activate trivy
This would allow you to deploy trivy into your cluster and use a K8sGPT analyzer to parse trivy results.`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func init() {
IntegrationCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to use for the integration")
}

63
cmd/integration/list.go Normal file
View File

@@ -0,0 +1,63 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package integration
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
)
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Short: "Lists built-in integrations",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
integrationProvider := integration.NewIntegration()
integrations := integrationProvider.List()
fmt.Println(color.YellowString("Active:"))
for _, i := range integrations {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if b {
fmt.Printf("> %s\n", color.GreenString(i))
}
}
fmt.Println(color.YellowString("Unused: "))
for _, i := range integrations {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if !b {
fmt.Printf("> %s\n", color.GreenString(i))
}
}
},
}
func init() {
IntegrationCmd.AddCommand(listCmd)
}

View File

@@ -1,22 +1,41 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"fmt"
"os"
"path/filepath"
"github.com/adrg/xdg"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/integration"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/client-go/util/homedir"
)
var (
cfgFile string
masterURL string
kubeconfig string
version string
cfgFile string
kubecontext string
kubeconfig string
version string
)
// rootCmd represents the base command when called without any subcommands
@@ -40,29 +59,23 @@ func Execute(v string) {
}
func init() {
performConfigMigrationIfNeeded()
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
var kubeconfigPath string
if home := homedir.HomeDir(); home != "" {
kubeconfigPath = filepath.Join(home, ".kube", "config")
}
rootCmd.AddCommand(auth.AuthCmd)
rootCmd.AddCommand(analyze.AnalyzeCmd)
rootCmd.AddCommand(filters.FiltersCmd)
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.git.yaml)")
rootCmd.PersistentFlags().StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
//Initialise the kubeconfig
kubernetesClient, err := kubernetes.NewClient(masterURL, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
}
viper.Set("kubernetesClient", kubernetesClient)
rootCmd.AddCommand(integration.IntegrationCmd)
rootCmd.AddCommand(serve.ServeCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)")
rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.")
}
// initConfig reads in config file and ENV variables if set.
@@ -71,18 +84,20 @@ func initConfig() {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// the config will belocated under `~/.config/k8sgpt/k8sgpt.yaml` on linux
configDir := filepath.Join(xdg.ConfigHome, "k8sgpt")
// Search config in home directory with name ".k8sgpt.git" (without extension).
viper.AddConfigPath(home)
viper.AddConfigPath(configDir)
viper.SetConfigType("yaml")
viper.SetConfigName(".k8sgpt")
viper.SetConfigName("k8sgpt")
viper.SafeWriteConfig()
}
viper.Set("kubecontext", kubecontext)
viper.Set("kubeconfig", kubeconfig)
viper.SetEnvPrefix("K8SGPT")
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
@@ -90,3 +105,44 @@ func initConfig() {
// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
func performConfigMigrationIfNeeded() {
oldConfig, err := getLegacyConfigFilePath()
cobra.CheckErr(err)
oldConfigExists, err := util.FileExists(oldConfig)
cobra.CheckErr(err)
newConfig := getConfigFilePath()
newConfigExists, err := util.FileExists(newConfig)
cobra.CheckErr(err)
configDir := filepath.Dir(newConfig)
err = util.EnsureDirExists(configDir)
cobra.CheckErr(err)
if oldConfigExists && newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Warning: Legacy config file at `%s` detected! This file will be ignored!", oldConfig))
return
}
if oldConfigExists && !newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Performing config file migration from `%s` to `%s`", oldConfig, newConfig))
err = os.Rename(oldConfig, newConfig)
cobra.CheckErr(err)
}
}
func getConfigFilePath() string {
return filepath.Join(xdg.ConfigHome, "k8sgpt", "k8sgpt.yaml")
}
func getLegacyConfigFilePath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".k8sgpt.yaml"), nil
}

109
cmd/serve/serve.go Normal file
View File

@@ -0,0 +1,109 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serve
import (
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
k8sgptserver "github.com/k8sgpt-ai/k8sgpt/pkg/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
port string
backend string
token string
)
var ServeCmd = &cobra.Command{
Use: "serve",
Short: "Runs k8sgpt as a server",
Long: `Runs k8sgpt as a server to allow for easy integration with other applications.`,
Run: func(cmd *cobra.Command, args []string) {
var configAI ai.AIConfiguration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
var aiProvider *ai.AIProvider
if len(configAI.Providers) == 0 {
// Check for env injection
backend = os.Getenv("K8SGPT_BACKEND")
password := os.Getenv("K8SGPT_PASSWORD")
model := os.Getenv("K8SGPT_MODEL")
baseURL := os.Getenv("K8SGPT_BASEURL")
// If the envs are set, allocate in place to the aiProvider
// else exit with error
envIsSet := backend != "" || password != "" || model != "" || baseURL != ""
if envIsSet {
aiProvider = &ai.AIProvider{
Name: backend,
Password: password,
Model: model,
BaseURL: baseURL,
}
configAI.Providers = append(configAI.Providers, *aiProvider)
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
} else {
color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth")
os.Exit(1)
}
}
if aiProvider == nil {
for _, provider := range configAI.Providers {
if backend == provider.Name {
aiProvider = &provider
break
}
}
}
if aiProvider.Name == "" {
color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
os.Exit(1)
}
server := k8sgptserver.Config{
Backend: aiProvider.Name,
Port: port,
Token: aiProvider.Password,
}
err = server.Serve()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// override the default backend if a flag is provided
},
}
func init() {
// add flag for backend
ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on")
ServeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
}

View File

@@ -1,3 +1,16 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
@@ -10,7 +23,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version number of k8sgpt",
Long: `All software has versions. This is k8sgpt's`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Printf("k8sgpt version %s", version)
cmd.Printf("k8sgpt version %s\n", version)
},
}

View File

@@ -1,4 +1,15 @@
FROM golang:1.20.2-alpine3.16 AS builder
# Copyright 2023 The K8sGPT Authors.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM golang:1.20.3-alpine3.16 AS builder
ENV CGO_ENABLED=0
@@ -16,8 +27,8 @@ FROM gcr.io/distroless/static AS production
LABEL org.opencontainers.image.source="https://github.com/k8sgpt-ai/k8sgpt" \
org.opencontainers.image.url="https://k8sgpt.ai" \
org.opencontainers.image.title="k8sgpt" \
org.opencontainers.image.vendor="the k8sgpt-ai maintainers" \
org.opencontainers.image.licenses="MIT"
org.opencontainers.image.vendor='The K8sGPT Authors' \
org.opencontainers.image.licenses='Apache-2.0'
WORKDIR /
COPY --from=builder /workspace/k8sgpt .

View File

@@ -0,0 +1,463 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 27,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 7,
"w": 2,
"x": 0,
"y": 0
},
"id": 8,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "![K8sGPT Logo](https://k8sgpt.ai/images/logo-white.png)",
"mode": "markdown"
},
"pluginVersion": "9.4.7",
"transparent": true,
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 4,
"w": 3,
"x": 2,
"y": 0
},
"id": 10,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "Useful links\n- [Website](https://k8sgpt.ai/)\n- [Documentation](https://docs.k8sgpt.ai/)\n- [GitHub](https://github.com/k8sgpt-ai)",
"mode": "markdown"
},
"pluginVersion": "9.4.7",
"transparent": true,
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Total number of errors",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 7,
"x": 5,
"y": 0
},
"id": 2,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showThresholdLabels": true,
"showThresholdMarkers": true
},
"pluginVersion": "9.4.7",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum(analyzer_errors{namespace=~\"$namespace\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Errors",
"type": "gauge"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Total number of errors per analyzer",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"displayName": "${__field.labels.analyzer_name}",
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 0
},
"id": 5,
"options": {
"displayMode": "gradient",
"minVizHeight": 10,
"minVizWidth": 0,
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "9.4.7",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by (analyzer_name) (analyzer_errors{namespace=~\"$namespace\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Errors per analyzer",
"type": "bargauge"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Total number of errors time series",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 4,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "9.4.7",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum (analyzer_errors{namespace=~\"$namespace\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Errors",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Total number of errors per analyzer time series",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineStyle": {
"fill": "solid"
},
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "right",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"pluginVersion": "9.4.7",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by (analyzer_name) (analyzer_errors{namespace=~\"$namespace\"})",
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Errors per analyzer",
"type": "timeseries"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {
"selected": true,
"text": [
"All"
],
"value": [
"$__all"
]
},
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"definition": "label_values(analyzer_errors, namespace)",
"hide": 0,
"includeAll": true,
"label": "Namespace",
"multi": true,
"name": "namespace",
"options": [],
"query": {
"query": "label_values(analyzer_errors, namespace)",
"refId": "StandardVariableQuery"
},
"refresh": 2,
"regex": "",
"skipUrlSync": false,
"sort": 1,
"type": "query"
}
]
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "K8sGPT",
"uid": "JfxtNBP4z",
"version": 1,
"weekStart": ""
}

View File

@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8sgpt-deployment
namespace: k8sgpt
labels:
app: k8sgpt
spec:
replicas: 1
selector:
matchLabels:
app: k8sgpt
template:
metadata:
labels:
app: k8sgpt
spec:
serviceAccountName: k8sgpt
containers:
- name: k8sgpt-container
imagePullPolicy: Always
image: ghcr.io/k8sgpt-ai/k8sgpt:v0.2.5 #x-release-please-version
ports:
- containerPort: 8080
args: ["serve"]
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.2"
memory: "156Mi"
env:
- name: K8SGPT_MODEL
value: "gpt-3.5-turbo"
- name: K8SGPT_BACKEND
value: "openai"
- name: K8SGPT_PASSWORD
valueFrom:
secretKeyRef:
name: ai-backend-secret
key: secret-key

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8sgpt-cluster-role-all
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- get
- list
- watch

View File

@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k8sgpt-rolebinding
namespace: k8sgpt
subjects:
- kind: ServiceAccount
name: k8sgpt
namespace: k8sgpt
roleRef:
kind: ClusterRole
name: k8sgpt-cluster-role-all
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8sgpt
namespace: k8sgpt

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: k8sgpt-service
namespace: k8sgpt
labels:
app: k8sgpt
spec:
selector:
app: k8sgpt
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP

View File

@@ -0,0 +1,15 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
app: k8sgpt
name: k8sgpt-service-monitor
namespace: k8sgpt
spec:
endpoints:
- honorLabels: true
path: /metrics
port: http
selector:
matchLabels:
app: k8sgpt

170
go.mod
View File

@@ -3,67 +3,181 @@ module github.com/k8sgpt-ai/k8sgpt
go 1.20
require (
github.com/aquasecurity/trivy-operator v0.13.1
github.com/fatih/color v1.15.0
github.com/sashabaranov/go-openai v1.5.7
github.com/magiconair/properties v1.8.7
github.com/mittwald/go-helm-client v0.12.1
github.com/sashabaranov/go-openai v1.9.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
golang.org/x/term v0.6.0
github.com/stretchr/testify v1.8.2
golang.org/x/term v0.7.0
helm.sh/helm/v3 v3.11.3
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/kubectl v0.26.3
)
require github.com/adrg/xdg v0.4.0
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/aquasecurity/defsec v0.86.0 // indirect
github.com/aquasecurity/go-dep-parser v0.0.0-20230413091456-df0396537e15 // indirect
github.com/aquasecurity/table v1.8.0 // indirect
github.com/aquasecurity/tml v0.6.1 // indirect
github.com/aquasecurity/trivy v0.40.0 // indirect
github.com/aquasecurity/trivy-db v0.0.0-20230411140759-3c2ee2168575 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/containerd v1.7.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v23.0.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/go-containerregistry v0.14.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/masahiro331/go-xfs-filesystem v0.0.0-20221225060805-c02764233454 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8 // indirect
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.15.0
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/robfig/cron/v3 v3.0.1
github.com/rubenv/sql-migrate v1.3.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spdx/tools-golang v0.5.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/time v0.1.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
k8s.io/apiextensions-apiserver v0.26.3 // indirect
k8s.io/apiserver v0.26.3 // indirect
k8s.io/cli-runtime v0.26.3 // indirect
k8s.io/component-base v0.26.3 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c // indirect
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
oras.land/oras-go v1.2.2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
replace oras.land/oras-go => oras.land/oras-go v1.2.3
// v0.3.1-0.20230104082527-d6f58551be3f is taken from github.com/moby/buildkit v0.11.0
// spdx logic write on v0.3.0 and incompatible with v0.3.1-0.20230104082527-d6f58551be3f
replace github.com/spdx/tools-golang => github.com/spdx/tools-golang v0.3.0

736
go.sum

File diff suppressed because it is too large Load Diff

BIN
images/demo5.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
images/nodes.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

12
main.go
View File

@@ -1,6 +1,16 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import "github.com/k8sgpt-ai/k8sgpt/cmd"

View File

@@ -1,48 +0,0 @@
package ai
import (
"context"
"errors"
"fmt"
"github.com/sashabaranov/go-openai"
)
const (
default_prompt = "Simplify the following Kubernetes error message and provide a solution in %s: %s"
prompt_a = "Read the following input %s and provide possible scenarios for remediation in %s"
prompt_b = "Considering the following input from the Kubernetes resource %s and the error message %s, provide possible scenarios for remediation in %s"
prompt_c = "Reading the following %s error message and it's accompanying log message %s, how would you simplify this message?"
)
type OpenAIClient struct {
client *openai.Client
language string
}
func (c *OpenAIClient) Configure(token string, language string) error {
client := openai.NewClient(token)
if client == nil {
return errors.New("error creating OpenAI client")
}
c.language = language
c.client = client
return nil
}
func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
// Create a completion request
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
Role: "user",
Content: fmt.Sprintf(default_prompt, c.language, prompt),
},
},
})
if err != nil {
return "", err
}
return resp.Choices[0].Message.Content, nil
}

View File

@@ -1,8 +1,67 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ai
import "context"
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
)
type IAI interface {
Configure(token string, language string) error
Configure(config IAIConfig, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
Parse(ctx context.Context, prompt []string, cache cache.ICache) (string, error)
GetName() string
}
type IAIConfig interface {
GetPassword() string
GetModel() string
GetBaseURL() string
}
func NewClient(provider string) IAI {
switch provider {
case "openai":
return &OpenAIClient{}
case "noopai":
return &NoOpAIClient{}
default:
return &OpenAIClient{}
}
}
type AIConfiguration struct {
Providers []AIProvider `mapstructure:"providers"`
}
type AIProvider struct {
Name string `mapstructure:"name"`
Model string `mapstructure:"model"`
Password string `mapstructure:"password"`
BaseURL string `mapstructure:"base_url"`
}
func (p *AIProvider) GetBaseURL() string {
return p.BaseURL
}
func (p *AIProvider) GetPassword() string {
return p.Password
}
func (p *AIProvider) GetModel() string {
return p.Model
}

72
pkg/ai/noopai.go Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ai
import (
"context"
"encoding/base64"
"fmt"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
)
type NoOpAIClient struct {
client string
language string
model string
}
func (c *NoOpAIClient) Configure(config IAIConfig, language string) error {
token := config.GetPassword()
c.language = language
c.client = fmt.Sprintf("I am a noop client with the token %s ", token)
c.model = config.GetModel()
return nil
}
func (c *NoOpAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
// Create a completion request
response := "I am a noop response to the prompt " + prompt
return response, nil
}
func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err != nil {
color.Red("error storing value to cache: %v", err)
return "", nil
}
return response, nil
}
func (a *NoOpAIClient) GetName() string {
return "noopai"
}

116
pkg/ai/openai.go Normal file
View File

@@ -0,0 +1,116 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ai
import (
"context"
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/sashabaranov/go-openai"
"github.com/fatih/color"
)
type OpenAIClient struct {
client *openai.Client
language string
model string
}
func (c *OpenAIClient) Configure(config IAIConfig, language string) error {
token := config.GetPassword()
defaultConfig := openai.DefaultConfig(token)
baseURL := config.GetBaseURL()
if baseURL != "" {
defaultConfig.BaseURL = baseURL
}
client := openai.NewClientWithConfig(defaultConfig)
if client == nil {
return errors.New("error creating OpenAI client")
}
c.language = language
c.client = client
c.model = config.GetModel()
return nil
}
func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
// Create a completion request
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: c.model,
Messages: []openai.ChatCompletionMessage{
{
Role: "user",
Content: fmt.Sprintf(default_prompt, c.language, prompt),
},
},
})
if err != nil {
return "", err
}
return resp.Choices[0].Message.Content, nil
}
func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache) (string, error) {
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
// find in viper cache
if cache.Exists(cacheKey) {
// retrieve data from cache
response, err := cache.Load(cacheKey)
if err != nil {
return "", err
}
if response == "" {
color.Red("error retrieving cached data")
return "", nil
}
output, err := base64.StdEncoding.DecodeString(response)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
}
return string(output), nil
}
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
return "", err
}
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err != nil {
color.Red("error storing value to cache: %v", err)
return "", nil
}
return response, nil
}
func (a *OpenAIClient) GetName() string {
return "openai"
}

8
pkg/ai/prompts.go Normal file
View File

@@ -0,0 +1,8 @@
package ai
const (
default_prompt = "Simplify the following Kubernetes error message and provide a solution in %s: %s"
prompt_a = "Read the following input %s and provide possible scenarios for remediation in %s"
prompt_b = "Considering the following input from the Kubernetes resource %s and the error message %s, provide possible scenarios for remediation in %s"
prompt_c = "Reading the following %s error message and it's accompanying log message %s, how would you simplify this message?"
)

264
pkg/analysis/analysis.go Normal file
View File

@@ -0,0 +1,264 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analysis
import (
"context"
"errors"
"fmt"
"os"
"reflect"
"strings"
"sync"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/schollz/progressbar/v3"
"github.com/spf13/viper"
)
type Analysis struct {
Context context.Context
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []common.Result
Namespace string
Cache cache.ICache
Explain bool
MaxConcurrency int
}
type AnalysisStatus string
const (
StateOK AnalysisStatus = "OK"
StateProblemDetected AnalysisStatus = "ProblemDetected"
)
type JsonOutput struct {
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []common.Result `json:"results"`
}
func NewAnalysis(backend string, language string, filters []string, namespace string, noCache bool, explain bool, maxConcurrency int) (*Analysis, error) {
var configAI ai.AIConfiguration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if len(configAI.Providers) == 0 && explain {
color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth")
os.Exit(1)
}
var aiProvider ai.AIProvider
for _, provider := range configAI.Providers {
if backend == provider.Name {
aiProvider = provider
break
}
}
if aiProvider.Name == "" {
color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
return nil, errors.New("AI provider not specified in configuration")
}
aiClient := ai.NewClient(aiProvider.Name)
if err := aiClient.Configure(&aiProvider, language); err != nil {
color.Red("Error: %v", err)
return nil, err
}
ctx := context.Background()
// Get kubernetes client from viper
kubecontext := viper.GetString("kubecontext")
kubeconfig := viper.GetString("kubeconfig")
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
return nil, err
}
return &Analysis{
Context: ctx,
Filters: filters,
Client: client,
AIClient: aiClient,
Namespace: namespace,
Cache: cache.New(noCache),
Explain: explain,
MaxConcurrency: maxConcurrency,
}, nil
}
func (a *Analysis) RunAnalysis() []error {
activeFilters := viper.GetStringSlice("active_filters")
analyzerMap := analyzer.GetAnalyzerMap()
analyzerConfig := common.Analyzer{
Client: a.Client,
Context: a.Context,
Namespace: a.Namespace,
AIClient: a.AIClient,
}
var errorList []error
semaphore := make(chan struct{}, a.MaxConcurrency)
// if there are no filters selected and no active_filters then run all of them
if len(a.Filters) == 0 && len(activeFilters) == 0 {
var wg sync.WaitGroup
var mutex sync.Mutex
for _, analyzer := range analyzerMap {
wg.Add(1)
semaphore <- struct{}{}
go func(analyzer common.IAnalyzer, wg *sync.WaitGroup, semaphore chan struct{}) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("[%s] %s", reflect.TypeOf(analyzer).Name(), err)))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer, &wg, semaphore)
}
wg.Wait()
return errorList
}
semaphore = make(chan struct{}, a.MaxConcurrency)
// if the filters flag is specified
if len(a.Filters) != 0 {
var wg sync.WaitGroup
var mutex sync.Mutex
for _, filter := range a.Filters {
if analyzer, ok := analyzerMap[filter]; ok {
semaphore <- struct{}{}
wg.Add(1)
go func(analyzer common.IAnalyzer) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("[%s] %s", filter, err)))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer)
} else {
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter)))
}
}
wg.Wait()
return errorList
}
var wg sync.WaitGroup
var mutex sync.Mutex
semaphore = make(chan struct{}, a.MaxConcurrency)
// use active_filters
for _, filter := range activeFilters {
if analyzer, ok := analyzerMap[filter]; ok {
semaphore <- struct{}{}
wg.Add(1)
go func(analyzer common.IAnalyzer) {
defer wg.Done()
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
errorList = append(errorList, fmt.Errorf("[%s] %s", filter, err))
mutex.Unlock()
}
mutex.Lock()
a.Results = append(a.Results, results...)
mutex.Unlock()
<-semaphore
}(analyzer)
}
}
wg.Wait()
return errorList
}
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
if len(a.Results) == 0 {
return nil
}
var bar *progressbar.ProgressBar
if output != "json" {
bar = progressbar.Default(int64(len(a.Results)))
}
for index, analysis := range a.Results {
var texts []string
for _, failure := range analysis.Error {
if anonymize {
for _, s := range failure.Sensitive {
failure.Text = util.ReplaceIfMatch(failure.Text, s.Unmasked, s.Masked)
}
}
texts = append(texts, failure.Text)
}
parsedText, err := a.AIClient.Parse(a.Context, texts, a.Cache)
if err != nil {
// FIXME: can we avoid checking if output is json multiple times?
// maybe implement the progress bar better?
if output != "json" {
bar.Exit()
}
// Check for exhaustion
if strings.Contains(err.Error(), "status code: 429") {
return fmt.Errorf("exhausted API quota for AI provider %s: %v", a.AIClient.GetName(), err)
} else {
return fmt.Errorf("failed while calling AI provider %s: %v", a.AIClient.GetName(), err)
}
}
if anonymize {
for _, failure := range analysis.Error {
for _, s := range failure.Sensitive {
parsedText = strings.ReplaceAll(parsedText, s.Masked, s.Unmasked)
}
}
}
analysis.Details = parsedText
if output != "json" {
bar.Add(1)
}
a.Results[index] = analysis
}
return nil
}

View File

@@ -0,0 +1,167 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analysis
import (
"encoding/json"
"fmt"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/stretchr/testify/require"
)
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []common.Result{},
Namespace: "default",
}
expected := JsonOutput{
Status: StateOK,
Problems: 0,
Results: []common.Result{},
}
gotJson, err := analysis.PrintOutput("json")
if err != nil {
t.Error(err)
}
got := JsonOutput{}
err = json.Unmarshal(gotJson, &got)
if err != nil {
t.Error(err)
}
fmt.Println(got)
fmt.Println(expected)
require.Equal(t, got, expected)
}
func TestAnalysis_ProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
Namespace: "default",
}
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 1,
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
}
gotJson, err := analysis.PrintOutput("json")
if err != nil {
t.Error(err)
}
got := JsonOutput{}
err = json.Unmarshal(gotJson, &got)
if err != nil {
t.Error(err)
}
fmt.Println(got)
fmt.Println(expected)
require.Equal(t, got, expected)
}
func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
{
Text: "another-test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
Namespace: "default",
}
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 2,
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
{
Text: "another-test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
}
gotJson, err := analysis.PrintOutput("json")
if err != nil {
t.Error(err)
}
got := JsonOutput{}
err = json.Unmarshal(gotJson, &got)
if err != nil {
t.Error(err)
}
fmt.Println(got)
fmt.Println(expected)
require.Equal(t, got, expected)
}

72
pkg/analysis/output.go Normal file
View File

@@ -0,0 +1,72 @@
package analysis
import (
"encoding/json"
"fmt"
"strings"
"github.com/fatih/color"
)
var outputFormats = map[string]func(*Analysis) ([]byte, error){
"json": (*Analysis).jsonOutput,
"text": (*Analysis).textOutput,
}
func getOutputFormats() []string {
formats := make([]string, 0, len(outputFormats))
for format := range outputFormats {
formats = append(formats, format)
}
return formats
}
func (a *Analysis) PrintOutput(format string) ([]byte, error) {
outputFunc, ok := outputFormats[format]
if !ok {
return nil, fmt.Errorf("unsupported output format: %s. Available format %s", format, strings.Join(getOutputFormats(), ","))
}
return outputFunc(a)
}
func (a *Analysis) jsonOutput() ([]byte, error) {
var problems int
var status AnalysisStatus
for _, result := range a.Results {
problems += len(result.Error)
}
if problems > 0 {
status = StateProblemDetected
} else {
status = StateOK
}
result := JsonOutput{
Problems: problems,
Results: a.Results,
Status: status,
}
output, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, fmt.Errorf("error marshalling json: %v", err)
}
return output, nil
}
func (a *Analysis) textOutput() ([]byte, error) {
var output strings.Builder
output.WriteString("\n")
if len(a.Results) == 0 {
output.WriteString(color.GreenString("No problems detected\n"))
return []byte(output.String()), nil
}
for n, result := range a.Results {
output.WriteString(fmt.Sprintf("%s %s(%s)\n", color.CyanString("%d", n),
color.YellowString(result.Name), color.CyanString(result.ParentObject)))
for _, err := range result.Error {
output.WriteString(fmt.Sprintf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text)))
}
output.WriteString(color.GreenString(result.Details + "\n"))
}
return []byte(output.String()), nil
}

View File

@@ -1,22 +0,0 @@
package analyzer
import (
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
)
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
}
type Analysis struct {
Kind string `json:"kind"`
Name string `json:"name"`
Error []string `json:"error"`
Details string `json:"details"`
ParentObject string `json:"parentObject"`
}

View File

@@ -1,75 +1,114 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"encoding/base64"
"strings"
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
func RunAnalysis(ctx context.Context, client *kubernetes.Client,
aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
var (
AnalyzerErrorsMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "analyzer_errors",
Help: "Number of errors detected by analyzer",
}, []string{"analyzer_name", "object_name", "namespace"})
)
err := AnalyzePod(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
err = AnalyzeReplicaSet(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
err = AnalyzePersistentVolumeClaim(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
err = AnalyzeEndpoints(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
return nil
var coreAnalyzerMap = map[string]common.IAnalyzer{
"Pod": PodAnalyzer{},
"Deployment": DeploymentAnalyzer{},
"ReplicaSet": ReplicaSetAnalyzer{},
"PersistentVolumeClaim": PvcAnalyzer{},
"Service": ServiceAnalyzer{},
"Ingress": IngressAnalyzer{},
"StatefulSet": StatefulSetAnalyzer{},
"CronJob": CronJobAnalyzer{},
"Node": NodeAnalyzer{},
}
func ParseViaAI(ctx context.Context, aiClient ai.IAI, prompt []string,
nocache bool) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
// find in viper cache
if viper.IsSet(sEnc) && !nocache {
// retrieve data from cache
response := viper.GetString(sEnc)
if response == "" {
color.Red("error retrieving cached data")
return "", nil
var additionalAnalyzerMap = map[string]common.IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
"NetworkPolicy": NetworkPolicyAnalyzer{},
}
func ListFilters() ([]string, []string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
}
additionalKeys := make([]string, 0, len(additionalAnalyzerMap))
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
}
integrationProvider := integration.NewIntegration()
var integrationAnalyzers []string
for _, i := range integrationProvider.List() {
b, _ := integrationProvider.IsActivate(i)
if b {
in, err := integrationProvider.Get(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
integrationAnalyzers = append(integrationAnalyzers, in.GetAnalyzerName())
}
output, err := base64.StdEncoding.DecodeString(response)
}
return coreKeys, additionalKeys, integrationAnalyzers
}
func GetAnalyzerMap() map[string]common.IAnalyzer {
mergedMap := make(map[string]common.IAnalyzer)
// add core analyzer
for key, value := range coreAnalyzerMap {
mergedMap[key] = value
}
// add additional analyzer
for key, value := range additionalAnalyzerMap {
mergedMap[key] = value
}
integrationProvider := integration.NewIntegration()
for _, i := range integrationProvider.List() {
b, err := integrationProvider.IsActivate(i)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
return string(output), nil
}
response, err := aiClient.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", nil
}
if !viper.IsSet(sEnc) {
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
if b {
in, err := integrationProvider.Get(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
in.AddAnalyzer(&mergedMap)
}
}
return response, nil
return mergedMap
}

132
pkg/analyzer/cronjob.go Normal file
View File

@@ -0,0 +1,132 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"time"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
cron "github.com/robfig/cron/v3"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CronJobAnalyzer struct{}
func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "CronJob"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
var results []common.Result
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{})
if err != nil {
return results, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, cronJob := range cronJobList.Items {
var failures []common.Failure
if cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("CronJob %s is suspended", cronJob.Name),
Sensitive: []common.Sensitive{
{
Unmasked: cronJob.Namespace,
Masked: util.MaskString(cronJob.Namespace),
},
{
Unmasked: cronJob.Name,
Masked: util.MaskString(cronJob.Name),
},
},
})
} else {
// check the schedule format
if _, err := CheckCronScheduleIsValid(cronJob.Spec.Schedule); err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("CronJob %s has an invalid schedule: %s", cronJob.Name, err.Error()),
Sensitive: []common.Sensitive{
{
Unmasked: cronJob.Namespace,
Masked: util.MaskString(cronJob.Namespace),
},
{
Unmasked: cronJob.Name,
Masked: util.MaskString(cronJob.Name),
},
},
})
}
// check the starting deadline
if cronJob.Spec.StartingDeadlineSeconds != nil {
deadline := time.Duration(*cronJob.Spec.StartingDeadlineSeconds) * time.Second
if deadline < 0 {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("CronJob %s has a negative starting deadline", cronJob.Name),
Sensitive: []common.Sensitive{
{
Unmasked: cronJob.Namespace,
Masked: util.MaskString(cronJob.Namespace),
},
{
Unmasked: cronJob.Name,
Masked: util.MaskString(cronJob.Name),
},
},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", cronJob.Namespace, cronJob.Name)] = common.PreAnalysis{
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, cronJob.Name, cronJob.Namespace).Set(float64(len(failures)))
}
for key, value := range preAnalysis {
currentAnalysis := common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
a.Results = append(results, currentAnalysis)
}
}
return a.Results, nil
}
// Check CRON schedule format
func CheckCronScheduleIsValid(schedule string) (bool, error) {
_, err := cron.ParseStandard(schedule)
if err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,233 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestCronJobSuccess(t *testing.T) {
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "default",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*/1 * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := CronJobAnalyzer{}
analysisResults, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 0)
}
func TestCronJobBroken(t *testing.T) {
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "default",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*** * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := CronJobAnalyzer{}
analysisResults, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
assert.Equal(t, analysisResults[0].Kind, "CronJob")
}
func TestCronJobBrokenMultipleNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "default",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*** * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
},
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "example-cronjob",
Namespace: "other-namespace",
Annotations: map[string]string{
"analysisDate": "2022-04-01",
},
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.CronJobSpec{
Schedule: "*** * * * *",
ConcurrencyPolicy: "Allow",
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "example-app",
},
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
},
},
RestartPolicy: v1.RestartPolicyOnFailure,
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := CronJobAnalyzer{}
analysisResults, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
assert.Equal(t, analysisResults[0].Kind, "CronJob")
}

View File

@@ -0,0 +1,82 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
)
// DeploymentAnalyzer is an analyzer that checks for misconfigured Deployments
type DeploymentAnalyzer struct {
}
// Analyze scans all namespaces for Deployments with misconfigurations
func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Deployment"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, deployment := range deployments.Items {
var failures []common.Failure
if *deployment.Spec.Replicas != deployment.Status.Replicas {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Deployment %s/%s has %d replicas but %d are available", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.Replicas),
Sensitive: []common.Sensitive{
{
Unmasked: deployment.Namespace,
Masked: util.MaskString(deployment.Namespace),
},
{
Unmasked: deployment.Name,
Masked: util.MaskString(deployment.Name),
},
}})
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name)] = common.PreAnalysis{
FailureDetails: failures,
Deployment: deployment,
}
AnalyzerErrorsMetric.WithLabelValues(kind, deployment.Name, deployment.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,153 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestDeploymentAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
Replicas: 2,
AvailableReplicas: 1,
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
deploymentAnalyzer := DeploymentAnalyzer{}
analysisResults, err := deploymentAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Kind, "Deployment")
assert.Equal(t, analysisResults[0].Name, "default/example")
}
func TestDeploymentAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
Replicas: 2,
AvailableReplicas: 1,
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example-container",
Image: "nginx",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
Status: appsv1.DeploymentStatus{
Replicas: 2,
AvailableReplicas: 1,
},
},
)
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
deploymentAnalyzer := DeploymentAnalyzer{}
analysisResults, err := deploymentAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
assert.Equal(t, analysisResults[0].Kind, "Deployment")
assert.Equal(t, analysisResults[0].Name, "default/example")
}

View File

@@ -1,3 +1,16 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
@@ -8,36 +21,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) {
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx,
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pod.Name,
})
if err != nil {
return nil, err
}
// find most recent event
var latestEvent *v1.Event
for _, event := range events.Items {
if latestEvent == nil {
latestEvent = &event
}
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
latestEvent = &event
}
}
return latestEvent, nil
}
func FetchLatestPvcEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pvc *v1.PersistentVolumeClaim) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(pvc.Namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + pvc.Name,
FieldSelector: "involvedObject.name=" + name,
})
if err != nil {

173
pkg/analyzer/hpa.go Normal file
View File

@@ -0,0 +1,173 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type HpaAnalyzer struct{}
func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "HorizontalPodAutoscaler"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, hpa := range list.Items {
var failures []common.Failure
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
var podInfo PodInfo
switch scaleTargetRef.Kind {
case "Deployment":
deployment, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = DeploymentInfo{deployment}
}
case "ReplicationController":
rc, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = ReplicationControllerInfo{rc}
}
case "ReplicaSet":
rs, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = ReplicaSetInfo{rs}
}
case "StatefulSet":
ss, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err == nil {
podInfo = StatefulSetInfo{ss}
}
default:
failures = append(failures, common.Failure{
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which is not an option.", scaleTargetRef.Kind),
Sensitive: []common.Sensitive{},
})
}
if podInfo == nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
Sensitive: []common.Sensitive{
{
Unmasked: scaleTargetRef.Name,
Masked: util.MaskString(scaleTargetRef.Name),
},
},
})
} else {
containers := len(podInfo.GetPodSpec().Containers)
for _, container := range podInfo.GetPodSpec().Containers {
if container.Resources.Requests == nil || container.Resources.Limits == nil {
containers--
}
}
if containers <= 0 {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s %s/%s does not have resource configured.", scaleTargetRef.Kind, a.Namespace, scaleTargetRef.Name),
Sensitive: []common.Sensitive{
{
Unmasked: scaleTargetRef.Name,
Masked: util.MaskString(scaleTargetRef.Name),
},
},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, hpa.Name, hpa.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}
type PodInfo interface {
GetPodSpec() corev1.PodSpec
}
type DeploymentInfo struct {
*appsv1.Deployment
}
func (d DeploymentInfo) GetPodSpec() corev1.PodSpec {
return d.Spec.Template.Spec
}
// define a structure for ReplicationController
type ReplicationControllerInfo struct {
*corev1.ReplicationController
}
func (rc ReplicationControllerInfo) GetPodSpec() corev1.PodSpec {
return rc.Spec.Template.Spec
}
// define a structure for ReplicaSet
type ReplicaSetInfo struct {
*appsv1.ReplicaSet
}
func (rs ReplicaSetInfo) GetPodSpec() corev1.PodSpec {
return rs.Spec.Template.Spec
}
// define a structure for StatefulSet
type StatefulSetInfo struct {
*appsv1.StatefulSet
}
// implement PodInfo for StatefulSetInfo
func (ss StatefulSetInfo) GetPodSpec() corev1.PodSpec {
return ss.Spec.Template.Spec
}

View File

@@ -0,0 +1,533 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"strings"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestHPAAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
})
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example-2",
Namespace: "default",
Annotations: map[string]string{},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 2)
}
func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "unsupported",
},
},
})
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "which is not an option.") {
errorFound = true
break
}
}
if errorFound {
break
}
}
if !errorFound {
t.Error("expected error 'does not possible option.' not found in analysis results")
}
}
func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "non-existent",
},
},
})
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "does not exist.") {
errorFound = true
break
}
}
if errorFound {
break
}
}
if !errorFound {
t.Error("expected error 'does not exist.' not found in analysis results")
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "example",
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "ReplicationController",
Name: "example",
},
},
},
&corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: corev1.ReplicationControllerSpec{
Template: &corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "ReplicaSet",
Name: "example",
},
},
},
&appsv1.ReplicaSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.ReplicaSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "StatefulSet",
Name: "example",
},
},
},
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.StatefulSetSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
for _, analysis := range analysisResults {
assert.Equal(t, len(analysis.Error), 0)
}
}
func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
Kind: "Deployment",
Name: "example",
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: appsv1.DeploymentSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Image: "nginx",
},
},
},
},
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "does not have resource configured."){
errorFound = true
break
}
if errorFound {
break
}
}
if !errorFound {
t.Error("expected error 'does not have resource configured.' not found in analysis results")
}
}
}
func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
})
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

148
pkg/analyzer/ingress.go Normal file
View File

@@ -0,0 +1,148 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type IngressAnalyzer struct{}
func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Ingress"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ing := range list.Items {
var failures []common.Failure
// get ingressClassName
ingressClassName := ing.Spec.IngressClassName
if ingressClassName == nil {
ingClassValue := ing.Annotations["kubernetes.io/ingress.class"]
if ingClassValue == "" {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name),
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
},
{
Unmasked: ing.Name,
Masked: util.MaskString(ing.Name),
},
},
})
} else {
ingressClassName = &ingClassValue
}
}
// check if ingressclass exist
if ingressClassName != nil {
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName),
Sensitive: []common.Sensitive{
{
Unmasked: *ingressClassName,
Masked: util.MaskString(*ingressClassName),
},
},
})
}
}
// loop over rules
for _, rule := range ing.Spec.Rules {
// loop over paths
for _, path := range rule.HTTP.Paths {
_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})
if err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name),
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
},
{
Unmasked: path.Backend.Service.Name,
Masked: util.MaskString(path.Backend.Service.Name),
},
},
})
}
}
}
for _, tls := range ing.Spec.TLS {
_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})
if err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName),
Sensitive: []common.Sensitive{
{
Unmasked: ing.Namespace,
Masked: util.MaskString(ing.Namespace),
},
{
Unmasked: tls.SecretName,
Masked: util.MaskString(tls.SecretName),
},
},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, ing.Name, ing.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,160 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"strings"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestIngressAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
})
ingressAnalyzer := IngressAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example-2",
Namespace: "default",
Annotations: map[string]string{},
},
},
)
ingressAnalyzer := IngressAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 2)
}
func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
})
ingressAnalyzer := IngressAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "does not specify an Ingress class") {
errorFound = true
break
}
}
if errorFound {
break
}
}
if !errorFound {
t.Error("expected error 'does not specify an Ingress class' not found in analysis results")
}
}
func TestIngressAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
})
ingressAnalyzer := IngressAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := ingressAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

96
pkg/analyzer/netpol.go Normal file
View File

@@ -0,0 +1,96 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type NetworkPolicyAnalyzer struct{}
func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "NetworkPolicy"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// get all network policies in the namespace
policies, err := a.Client.GetClient().NetworkingV1().
NetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, policy := range policies.Items {
var failures []common.Failure
// Check if policy allows traffic to all pods in the namespace
if len(policy.Spec.PodSelector.MatchLabels) == 0 {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Network policy allows traffic to all pods: %s", policy.Name),
Sensitive: []common.Sensitive{
{
Unmasked: policy.Name,
Masked: util.MaskString(policy.Name),
},
},
})
continue
}
// Check if policy is not applied to any pods
podList, err := util.GetPodListByLabels(a.Client.GetClient(), a.Namespace, policy.Spec.PodSelector.MatchLabels)
if err != nil {
return nil, err
}
if len(podList.Items) == 0 {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Network policy is not applied to any pods: %s", policy.Name),
Sensitive: []common.Sensitive{
{
Unmasked: policy.Name,
Masked: util.MaskString(policy.Name),
},
},
})
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", policy.Namespace, policy.Name)] = common.PreAnalysis{
FailureDetails: failures,
NetworkPolicy: policy,
}
AnalyzerErrorsMetric.WithLabelValues(kind, policy.Name, policy.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
currentAnalysis := common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

209
pkg/analyzer/netpol_test.go Normal file
View File

@@ -0,0 +1,209 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestNetpolNoPods(t *testing.T) {
clientset := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := NetworkPolicyAnalyzer{}
results, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(results), 1)
assert.Equal(t, results[0].Kind, "NetworkPolicy")
}
func TestNetpolWithPod(t *testing.T) {
clientset := fake.NewSimpleClientset(&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
}, &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Labels: map[string]string{
"app": "example",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "example",
Image: "example",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := NetworkPolicyAnalyzer{}
results, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(results), 0)
}
func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
},
&networkingv1.NetworkPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
Spec: networkingv1.NetworkPolicySpec{
PodSelector: metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "example",
},
},
Ingress: []networkingv1.NetworkPolicyIngressRule{
{
From: []networkingv1.NetworkPolicyPeer{
{
PodSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "database",
},
},
},
},
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analyzer := NetworkPolicyAnalyzer{}
results, err := analyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(results), 1)
assert.Equal(t, results[0].Kind, "NetworkPolicy")
}

96
pkg/analyzer/node.go Normal file
View File

@@ -0,0 +1,96 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
v1 "k8s.io/api/core/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type NodeAnalyzer struct{}
func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Node"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, node := range list.Items {
var failures []common.Failure
for _, nodeCondition := range node.Status.Conditions {
// https://kubernetes.io/docs/concepts/architecture/nodes/#condition
switch nodeCondition.Type {
case v1.NodeReady:
if nodeCondition.Status == v1.ConditionTrue {
break
}
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
default:
if nodeCondition.Status != v1.ConditionFalse {
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s", node.Name)] = common.PreAnalysis{
Node: node,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, node.Name, "").Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Node.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, err
}
func addNodeConditionFailure(failures []common.Failure, nodeName string, nodeCondition v1.NodeCondition) []common.Failure {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s has condition of type %s, reason %s: %s", nodeName, nodeCondition.Type, nodeCondition.Reason, nodeCondition.Message),
Sensitive: []common.Sensitive{
{
Unmasked: nodeName,
Masked: util.MaskString(nodeName),
},
},
})
return failures
}

124
pkg/analyzer/node_test.go Normal file
View File

@@ -0,0 +1,124 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestNodeAnalyzerNodeReady(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: v1.NodeReady,
Status: v1.ConditionTrue,
Reason: "KubeletReady",
Message: "kubelet is posting ready status",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 0)
}
func TestNodeAnalyzerNodeDiskPressure(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: v1.NodeDiskPressure,
Status: v1.ConditionTrue,
Reason: "KubeletHasDiskPressure",
Message: "kubelet has disk pressure",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
// A cloud provider may set their own condition and/or a new status might be introduced
// In such cases a failure is assumed and the code shouldn't break, although it might be a false positive
func TestNodeAnalyzerNodeUnknownType(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
Status: v1.NodeStatus{
Conditions: []v1.NodeCondition{
{
Type: "UnknownNodeConditionType",
Status: "CompletelyUnknown",
Reason: "KubeletHasTheUnknown",
Message: "kubelet has the unknown",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
}
nodeAnalyzer := NodeAnalyzer{}
var analysisResults []common.Result
analysisResults, err := nodeAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

102
pkg/analyzer/pdb.go Normal file
View File

@@ -0,0 +1,102 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PdbAnalyzer struct{}
func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "PodDisruptionBudget"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pdb := range list.Items {
var failures []common.Failure
evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "NoPods" && evt.Message != "" {
if pdb.Spec.Selector != nil {
for k, v := range pdb.Spec.Selector.MatchLabels {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v),
Sensitive: []common.Sensitive{
{
Unmasked: k,
Masked: util.MaskString(k),
},
{
Unmasked: v,
Masked: util.MaskString(v),
},
},
})
}
for _, v := range pdb.Spec.Selector.MatchExpressions {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, expected expression %s", evt.Message, v),
Sensitive: []common.Sensitive{},
})
}
} else {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, selector is nil", evt.Message),
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = common.PreAnalysis{
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pdb.Name, pdb.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, err
}

127
pkg/analyzer/pod.go Normal file
View File

@@ -0,0 +1,127 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PodAnalyzer struct {
}
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Pod"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pod := range list.Items {
var failures []common.Failure
// Check for pending pods
if pod.Status.Phase == "Pending" {
// Check through container status to check for crashes
for _, containerStatus := range pod.Status.Conditions {
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
if containerStatus.Message != "" {
failures = append(failures, common.Failure{
Text: containerStatus.Message,
Sensitive: []common.Sensitive{},
})
}
}
}
}
// Check through container status to check for crashes or unready
for _, containerStatus := range pod.Status.ContainerStatuses {
if containerStatus.State.Waiting != nil {
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
if containerStatus.State.Waiting.Message != "" {
failures = append(failures, common.Failure{
Text: containerStatus.State.Waiting.Message,
Sensitive: []common.Sensitive{},
})
}
}
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []common.Sensitive{},
})
}
}
} else {
// when pod is Running but its ReadinessProbe fails
if containerStatus.Ready == false && pod.Status.Phase == "Running" {
// parse the event log and append details
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "Unhealthy" && evt.Message != "" {
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []common.Sensitive{},
})
}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
Pod: pod,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -1,80 +0,0 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Pods("").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, pod := range list.Items {
var failures []string
// Check for pending pods
if pod.Status.Phase == "Pending" {
// Check through container status to check for crashes
for _, containerStatus := range pod.Status.Conditions {
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
if containerStatus.Message != "" {
failures = []string{containerStatus.Message}
}
}
}
}
// Check through container status to check for crashes
for _, containerStatus := range pod.Status.ContainerStatuses {
if containerStatus.State.Waiting != nil {
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
if containerStatus.State.Waiting.Message != "" {
failures = append(failures, containerStatus.State.Waiting.Message)
}
}
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPodEvent(ctx, client, &pod)
if err != nil || evt == nil {
continue
}
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
failures = append(failures, evt.Message)
}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = PreAnalysis{
Pod: pod,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "Pod",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

158
pkg/analyzer/pod_test.go Normal file
View File

@@ -0,0 +1,158 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestPodAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
},
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example2",
Namespace: "default",
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
ContainerStatuses: []v1.ContainerStatus{
{
Name: "example2",
Ready: false,
},
},
Conditions: []v1.PodCondition{
{
Type: v1.ContainersReady,
Reason: "ContainersNotReady",
Message: "containers with unready status: [example2]",
},
},
},
},
// simulate event: 30s Warning Unhealthy pod/my-nginx-7fb4dbcf47-4ch4w Readiness probe failed: bash: xxxx: command not found
&v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
InvolvedObject: v1.ObjectReference{
Kind: "Pod",
Name: "example2",
Namespace: "default",
UID: "differentUid",
APIVersion: "v1",
},
Reason: "Unhealthy",
Message: "readiness probe failed: the detail reason here ...",
Source: v1.EventSource{Component: "eventTest"},
Count: 1,
Type: v1.EventTypeWarning,
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []common.Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 2)
}
func TestPodAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
},
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []common.Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

82
pkg/analyzer/pvc.go Normal file
View File

@@ -0,0 +1,82 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type PvcAnalyzer struct{}
func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "PersistentVolumeClaim"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pvc := range list.Items {
var failures []common.Failure
// Check for empty rs
if pvc.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)
if err != nil || evt == nil {
continue
}
if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pvc.Name, pvc.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -1,59 +0,0 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePersistentVolumeClaim(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, pvc := range list.Items {
var failures []string
// Check for empty rs
if pvc.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestPvcEvent(ctx, client, &pvc)
if err != nil || evt == nil {
continue
}
if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
failures = append(failures, evt.Message)
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = PreAnalysis{
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "PersistentVolumeClaim",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.PersistentVolumeClaim.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

80
pkg/analyzer/rs.go Normal file
View File

@@ -0,0 +1,80 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ReplicaSetAnalyzer struct{}
func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "ReplicaSet"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, rs := range list.Items {
var failures []common.Failure
// Check for empty rs
if rs.Status.Replicas == 0 {
// Check through container status to check for crashes
for _, rsStatus := range rs.Status.Conditions {
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
failures = append(failures, common.Failure{
Text: rsStatus.Message,
Sensitive: []common.Sensitive{},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
ReplicaSet: rs,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, rs.Name, rs.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -1,57 +0,0 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, rs := range list.Items {
var failures []string
// Check for empty rs
if rs.Status.Replicas == 0 {
// Check through container status to check for crashes
for _, rsStatus := range rs.Status.Conditions {
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
failures = []string{rsStatus.Message}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = PreAnalysis{
ReplicaSet: rs,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "ReplicaSet",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.ReplicaSet.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

109
pkg/analyzer/service.go Normal file
View File

@@ -0,0 +1,109 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ServiceAnalyzer struct{}
func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Service"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ep := range list.Items {
var failures []common.Failure
// Check for empty service
if len(ep.Subsets) == 0 {
svc, err := a.Client.GetClient().CoreV1().Services(ep.Namespace).Get(a.Context, ep.Name, metav1.GetOptions{})
if err != nil {
color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name)
continue
}
for k, v := range svc.Spec.Selector {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v),
Sensitive: []common.Sensitive{
{
Unmasked: k,
Masked: util.MaskString(k),
},
{
Unmasked: v,
Masked: util.MaskString(v),
},
},
})
}
} else {
count := 0
pods := []string{}
// Check through container status to check for crashes
for _, epSubset := range ep.Subsets {
if len(epSubset.NotReadyAddresses) > 0 {
for _, addresses := range epSubset.NotReadyAddresses {
count++
pods = append(pods, addresses.TargetRef.Kind+"/"+addresses.TargetRef.Name)
}
failures = append(failures, common.Failure{
Text: fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count),
Sensitive: []common.Sensitive{},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = common.PreAnalysis{
Endpoint: ep,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, ep.Name, ep.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -1,72 +0,0 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzeEndpoints(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Endpoints("").List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, ep := range list.Items {
var failures []string
// Check for empty service
if len(ep.Subsets) == 0 {
svc, err := client.GetClient().CoreV1().Services(ep.Namespace).Get(ctx, ep.Name, metav1.GetOptions{})
if err != nil {
return err
}
for k, v := range svc.Spec.Selector {
failures = append(failures, fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v))
}
} else {
count := 0
pods := []string{}
// Check through container status to check for crashes
for _, epSubset := range ep.Subsets {
if len(epSubset.NotReadyAddresses) > 0 {
for _, addresses := range epSubset.NotReadyAddresses {
count++
pods = append(pods, addresses.TargetRef.Kind+"/"+addresses.TargetRef.Name)
}
failures = append(failures, fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count))
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = PreAnalysis{
Endpoint: ep,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "Service",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Endpoint.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

View File

@@ -0,0 +1,122 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestServiceAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": "example",
},
}})
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
serviceAnalyzer := ServiceAnalyzer{}
analysisResults, err := serviceAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
func TestServiceAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": "example",
},
},
},
&v1.Endpoints{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
Annotations: map[string]string{},
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"app": "example",
},
},
},
)
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
serviceAnalyzer := ServiceAnalyzer{}
analysisResults, err := serviceAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

101
pkg/analyzer/statefulset.go Normal file
View File

@@ -0,0 +1,101 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type StatefulSetAnalyzer struct{}
func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "StatefulSet"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
for _, sts := range list.Items {
var failures []common.Failure
// get serviceName
serviceName := sts.Spec.ServiceName
_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})
if err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName),
Sensitive: []common.Sensitive{
{
Unmasked: sts.Namespace,
Masked: util.MaskString(sts.Namespace),
},
{
Unmasked: serviceName,
Masked: util.MaskString(serviceName),
},
},
})
}
if len(sts.Spec.VolumeClaimTemplates) > 0 {
for _, volumeClaimTemplate := range sts.Spec.VolumeClaimTemplates {
if volumeClaimTemplate.Spec.StorageClassName != nil {
_, err := a.Client.GetClient().StorageV1().StorageClasses().Get(a.Context, *volumeClaimTemplate.Spec.StorageClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, common.Failure{
Text: fmt.Sprintf("StatefulSet uses the storage class %s which does not exist.", *volumeClaimTemplate.Spec.StorageClassName),
Sensitive: []common.Sensitive{
{
Unmasked: *volumeClaimTemplate.Spec.StorageClassName,
Masked: util.MaskString(*volumeClaimTemplate.Spec.StorageClassName),
},
},
})
}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = common.PreAnalysis{
StatefulSet: sts,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, sts.Name, sts.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,190 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package analyzer
import (
"context"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestStatefulSetAnalyzer(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.StatefulSetSpec{
ServiceName: "example-svc",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
want := "StatefulSet uses the service default/example-svc which does not exist."
for _, analysis := range analysisResults {
for _, got := range analysis.Error {
if want == got.Text {
errorFound = true
}
}
if errorFound {
break
}
}
if !errorFound {
t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want)
}
}
func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
storageClassName := "example-sc"
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Spec: appsv1.StatefulSetSpec{
ServiceName: "example-svc",
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-example",
},
Spec: corev1.PersistentVolumeClaimSpec{
StorageClassName: &storageClassName,
AccessModes: []corev1.PersistentVolumeAccessMode{
"ReadWriteOnce",
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse("1Gi"),
},
},
},
},
},
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
var errorFound bool
want := "StatefulSet uses the storage class example-sc which does not exist."
for _, analysis := range analysisResults {
for _, got := range analysis.Error {
if want == got.Text {
errorFound = true
}
}
if errorFound {
break
}
}
if !errorFound {
t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want)
}
}
func TestStatefulSetAnalyzerNamespaceFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
},
&appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "other-namespace",
},
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := statefulSetAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}

15
pkg/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,15 @@
package cache
type ICache interface {
Store(key string, data string) error
Load(key string) (string, error)
Exists(key string) bool
}
func New(noCache bool) ICache {
if noCache {
return &NoopCache{}
}
return &FileBasedCache{}
}

58
pkg/cache/file_based.go vendored Normal file
View File

@@ -0,0 +1,58 @@
package cache
import (
"fmt"
"os"
"path/filepath"
"github.com/adrg/xdg"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
)
var _ (ICache) = (*FileBasedCache)(nil)
type FileBasedCache struct{}
func (*FileBasedCache) Exists(key string) bool {
path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
if err != nil {
fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err)
return false
}
exists, err := util.FileExists(path)
if err != nil {
fmt.Fprintln(os.Stderr, "warning: error while testing if cache key exists:", err)
return false
}
return exists
}
func (*FileBasedCache) Load(key string) (string, error) {
path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
if err != nil {
return "", err
}
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(data), nil
}
func (*FileBasedCache) Store(key string, data string) error {
path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))
if err != nil {
return err
}
return os.WriteFile(path, []byte(data), 0600)
}

17
pkg/cache/noop.go vendored Normal file
View File

@@ -0,0 +1,17 @@
package cache
var _ (ICache) = (*NoopCache)(nil)
type NoopCache struct{}
func (c *NoopCache) Store(key string, data string) error {
return nil
}
func (c *NoopCache) Load(key string) (string, error) {
return "", nil
}
func (c *NoopCache) Exists(key string) bool {
return false
}

75
pkg/common/types.go Normal file
View File

@@ -0,0 +1,75 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"context"
trivy "github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
appsv1 "k8s.io/api/apps/v1"
autov1 "k8s.io/api/autoscaling/v1"
v1 "k8s.io/api/core/v1"
networkv1 "k8s.io/api/networking/v1"
policyv1 "k8s.io/api/policy/v1"
)
type IAnalyzer interface {
Analyze(analysis Analyzer) ([]Result, error)
}
type Analyzer struct {
Client *kubernetes.Client
Context context.Context
Namespace string
AIClient ai.IAI
PreAnalysis map[string]PreAnalysis
Results []Result
}
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []Failure
Deployment appsv1.Deployment
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkv1.Ingress
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
StatefulSet appsv1.StatefulSet
NetworkPolicy networkv1.NetworkPolicy
Node v1.Node
// Integrations
TrivyVulnerabilityReport trivy.VulnerabilityReport
}
type Result struct {
Kind string `json:"kind"`
Name string `json:"name"`
Error []Failure `json:"error"`
Details string `json:"details"`
ParentObject string `json:"parentObject"`
}
type Failure struct {
Text string
Sensitive []Sensitive
}
type Sensitive struct {
Unmasked string
Masked string
}

View File

@@ -0,0 +1,142 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package integration
import (
"errors"
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/viper"
)
type IIntegration interface {
// Add adds an integration to the cluster
Deploy(namespace string) error
// Remove removes an integration from the cluster
UnDeploy(namespace string) error
//
AddAnalyzer(*map[string]common.IAnalyzer)
// RemoveAnalyzer removes an analyzer from the cluster
RemoveAnalyzer() error
GetAnalyzerName() string
IsActivate() bool
}
type Integration struct {
}
var integrations = map[string]IIntegration{
"trivy": trivy.NewTrivy(),
}
func NewIntegration() *Integration {
return &Integration{}
}
func (*Integration) List() []string {
keys := make([]string, 0, len(integrations))
for k := range integrations {
keys = append(keys, k)
}
return keys
}
func (*Integration) Get(name string) (IIntegration, error) {
if _, ok := integrations[name]; !ok {
return nil, errors.New("integration not found")
}
return integrations[name], nil
}
func (*Integration) Activate(name string, namespace string) error {
if _, ok := integrations[name]; !ok {
return errors.New("integration not found")
}
// Update filters
activeFilters := viper.GetStringSlice("active_filters")
mergedFilters := append(activeFilters, integrations[name].GetAnalyzerName())
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
// Verify dupplicate
if len(dupplicatedFilters) != 0 {
color.Red("Integration already activated : %s", strings.Join(dupplicatedFilters, ", "))
os.Exit(1)
}
viper.Set("active_filters", uniqueFilters)
if err := integrations[name].Deploy(namespace); err != nil {
return err
}
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
return nil
}
func (*Integration) Deactivate(name string, namespace string) error {
if _, ok := integrations[name]; !ok {
return errors.New("integration not found")
}
activeFilters := viper.GetStringSlice("active_filters")
// Update filters
// This might be a bad idea, but we cannot reference analyzer here
foundFilter := false
for i, v := range activeFilters {
if v == integrations[name].GetAnalyzerName() {
foundFilter = true
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
break
}
}
if !foundFilter {
color.Red("Ingregation %s does not exist in configuration file. Please use k8sgpt integration add.", name)
os.Exit(1)
}
if err := integrations[name].UnDeploy(namespace); err != nil {
return err
}
viper.Set("active_filters", activeFilters)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
return nil
}
func (*Integration) IsActivate(name string) (bool, error) {
if _, ok := integrations[name]; !ok {
return false, errors.New("integration not found")
}
return integrations[name].IsActivate(), nil
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package trivy
import (
"fmt"
"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"k8s.io/client-go/rest"
)
type TrivyAnalyzer struct {
}
func (TrivyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// Get all trivy VulnerabilityReports
result := &v1alpha1.VulnerabilityReportList{}
config := a.Client.GetConfig()
// Add group version to sceheme
config.ContentConfig.GroupVersion = &v1alpha1.SchemeGroupVersion
config.UserAgent = rest.DefaultKubernetesUserAgent()
config.APIPath = "/apis"
restClient, err := rest.UnversionedRESTClientFor(config)
if err != nil {
return nil, err
}
err = restClient.Get().Resource("vulnerabilityreports").Do(a.Context).Into(result)
if err != nil {
return nil, err
}
// Find criticals and get CVE
var preAnalysis = map[string]common.PreAnalysis{}
for _, report := range result.Items {
// For each pod there may be multiple vulnerabilities
var failures []common.Failure
for _, vuln := range report.Report.Vulnerabilities {
if vuln.Severity == "CRITICAL" {
// get the vulnerability ID
// get the vulnerability description
failures = append(failures, common.Failure{
Text: fmt.Sprintf("critical Vulnerability found ID: %s (learn more at: %s)", vuln.VulnerabilityID, vuln.PrimaryLink),
Sensitive: []common.Sensitive{},
})
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", report.Labels["trivy-operator.resource.namespace"],
report.Labels["trivy-operator.resource.name"])] = common.PreAnalysis{
TrivyVulnerabilityReport: report,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: "VulnerabilityReport",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.TrivyVulnerabilityReport.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
}
return a.Results, nil
}

View File

@@ -0,0 +1,116 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package trivy
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
helmclient "github.com/mittwald/go-helm-client"
"helm.sh/helm/v3/pkg/repo"
)
const (
Repo = "https://aquasecurity.github.io/helm-charts/"
Version = "0.13.0"
ChartName = "trivy-operator"
RepoShortName = "aqua"
ReleaseName = "trivy-operator-k8sgpt"
)
type Trivy struct {
helm helmclient.Client
}
func NewTrivy() *Trivy {
helmClient, err := helmclient.New(&helmclient.Options{})
if err != nil {
panic(err)
}
return &Trivy{
helm: helmClient,
}
}
func (t *Trivy) GetAnalyzerName() string {
return "VulnerabilityReport"
}
func (t *Trivy) Deploy(namespace string) error {
// Add the repository
chartRepo := repo.Entry{
Name: RepoShortName,
URL: Repo,
}
// Add a chart-repository to the client.
if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil {
panic(err)
}
chartSpec := helmclient.ChartSpec{
ReleaseName: ReleaseName,
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
Namespace: namespace,
UpgradeCRDs: true,
Wait: false,
Timeout: 300,
}
// Install a chart release.
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
if _, err := t.helm.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {
return err
}
return nil
}
func (t *Trivy) UnDeploy(namespace string) error {
chartSpec := helmclient.ChartSpec{
ReleaseName: ReleaseName,
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
Namespace: namespace,
UpgradeCRDs: true,
Wait: false,
Timeout: 300,
}
// Uninstall the chart release.
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
if err := t.helm.UninstallRelease(&chartSpec); err != nil {
return err
}
return nil
}
func (t *Trivy) IsActivate() bool {
if _, err := t.helm.GetRelease(ReleaseName); err != nil {
return false
}
return true
}
func (t *Trivy) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
(*mergedMap)["VulnerabilityReport"] = &TrivyAnalyzer{}
}
func (t *Trivy) RemoveAnalyzer() error {
return nil
}

View File

@@ -1,26 +1,56 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubernetes
import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/scheme"
)
type Client struct {
client *kubernetes.Clientset
Client kubernetes.Interface
RestClient rest.Interface
Config *rest.Config
}
func (c *Client) GetClient() *kubernetes.Clientset {
return c.client
func (c *Client) GetConfig() *rest.Config {
return c.Config
}
func NewClient(masterURL string, kubeconfig string) (*Client, error) {
func (c *Client) GetClient() kubernetes.Interface {
return c.Client
}
func (c *Client) GetRestClient() rest.Interface {
return c.RestClient
}
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
var config *rest.Config
config, err := rest.InClusterConfig()
if err != nil {
kubeconfig :=
clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
config, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{
CurrentContext: kubecontext,
})
// create the clientset
config, err = clientConfig.ClientConfig()
if err != nil {
return nil, err
}
@@ -29,7 +59,18 @@ func NewClient(masterURL string, kubeconfig string) (*Client, error) {
if err != nil {
return nil, err
}
config.APIPath = "/api"
config.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
restClient, err := rest.RESTClientFor(config)
if err != nil {
return nil, err
}
return &Client{
client: clientSet,
Client: clientSet,
RestClient: restClient,
Config: config,
}, nil
}

77
pkg/server/log.go Normal file
View File

@@ -0,0 +1,77 @@
package server
import (
"bytes"
"net/http"
"time"
"go.uber.org/zap"
)
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
buf *bytes.Buffer
}
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
return &loggingResponseWriter{
w,
http.StatusOK,
&bytes.Buffer{},
}
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
return lrw.buf.Write(b)
}
func (lrw *loggingResponseWriter) Flush() {
if f, ok := lrw.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
lrw.ResponseWriter.Write(lrw.buf.Bytes())
}
func logRequest(logger *zap.Logger, fields []zap.Field, statusCode int, message string) {
if statusCode >= 400 {
logger.Error(message, fields...)
} else {
logger.Info("request completed", fields...)
}
}
func loggingMiddleware(next http.Handler) http.Handler {
config := zap.NewProductionConfig()
config.DisableCaller = true
logger, err := config.Build()
if err != nil {
panic(err)
}
defer logger.Sync()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
lrw := NewLoggingResponseWriter(w)
start := time.Now()
defer func() {
duration := time.Since(start).Milliseconds()
fields := []zap.Field{
zap.Int64("duration_ms", duration),
zap.String("method", r.Method),
zap.String("remote_addr", r.RemoteAddr),
zap.Int("status_code", lrw.statusCode),
zap.String("url", r.URL.Path),
}
logRequest(logger, fields, lrw.statusCode, lrw.buf.String())
}()
next.ServeHTTP(lrw, r)
lrw.Flush()
})
}

138
pkg/server/server.go Normal file
View File

@@ -0,0 +1,138 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package server
import (
json "encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Config struct {
Port string
Backend string
Key string
Token string
Output string
maxConcurrency int
}
type Health struct {
Status string `json:"status"`
Success int `json:"success"`
Failure int `json:"failure"`
}
var health = Health{
Status: "ok",
Success: 0,
Failure: 0,
}
type Result struct {
Analysis []analysis.Analysis `json:"analysis"`
}
func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) {
namespace := r.URL.Query().Get("namespace")
explain := getBoolParam(r.URL.Query().Get("explain"))
anonymize := getBoolParam(r.URL.Query().Get("anonymize"))
nocache := getBoolParam(r.URL.Query().Get("nocache"))
language := r.URL.Query().Get("language")
var err error
s.maxConcurrency, err = strconv.Atoi(r.URL.Query().Get("maxConcurrency"))
if err != nil {
s.maxConcurrency = 10
}
s.Output = r.URL.Query().Get("output")
if s.Output == "" {
s.Output = "json"
}
config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain, s.maxConcurrency)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
analysisErrors := config.RunAnalysis()
if analysisErrors != nil {
var errorMessage string
for _, err := range analysisErrors {
errorMessage += err.Error() + "\n"
}
http.Error(w, errorMessage, http.StatusInternalServerError)
health.Failure++
}
if explain {
err := config.GetAIResults(s.Output, anonymize)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
out, err := config.PrintOutput(s.Output)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
health.Success++
fmt.Fprintf(w, string(out))
}
func (s *Config) Serve() error {
handler := loggingMiddleware(http.DefaultServeMux)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/analyze", s.analyzeHandler)
http.HandleFunc("/healthz", s.healthzHandler)
color.Green("Starting server on port %s", s.Port)
err := http.ListenAndServe(":"+s.Port, handler)
if err != nil {
fmt.Printf("error starting server: %s\n", err)
return err
}
return nil
}
func (s *Config) healthzHandler(w http.ResponseWriter, r *http.Request) {
js, err := json.MarshalIndent(health, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, string(js))
}
func getBoolParam(param string) bool {
b, err := strconv.ParseBool(strings.ToLower(param))
if err != nil {
// Handle error if conversion fails
return false
}
return b
}

View File

@@ -1,12 +1,44 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"context"
"encoding/base64"
"errors"
"fmt"
"math/rand"
"os"
"regexp"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k "k8s.io/client-go/kubernetes"
)
var anonymizePattern = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;':\",./<>?")
func SliceContainsString(slice []string, s string) bool {
for _, item := range slice {
if item == s {
return true
}
}
return false
}
func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) {
if meta.OwnerReferences != nil {
for _, owner := range meta.OwnerReferences {
@@ -50,8 +82,106 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
return GetParent(client, ds.ObjectMeta)
}
return "DaemonSet/" + ds.Name, false
case "Ingress":
ds, err := client.GetClient().NetworkingV1().Ingresses(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
if err != nil {
return "", false
}
if ds.OwnerReferences != nil {
return GetParent(client, ds.ObjectMeta)
}
return "Ingress/" + ds.Name, false
}
}
}
return meta.Name, false
}
func RemoveDuplicates(slice []string) ([]string, []string) {
set := make(map[string]bool)
duplicates := []string{}
for _, val := range slice {
if _, ok := set[val]; !ok {
set[val] = true
} else {
duplicates = append(duplicates, val)
}
}
uniqueSlice := make([]string, 0, len(set))
for val := range set {
uniqueSlice = append(uniqueSlice, val)
}
return uniqueSlice, duplicates
}
func SliceDiff(source, dest []string) []string {
mb := make(map[string]struct{}, len(dest))
for _, x := range dest {
mb[x] = struct{}{}
}
var diff []string
for _, x := range source {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
func MaskString(input string) string {
key := make([]byte, len(input))
result := make([]rune, len(input))
rand.Read(key)
for i := range result {
result[i] = anonymizePattern[int(key[i])%len(anonymizePattern)]
}
return base64.StdEncoding.EncodeToString([]byte(string(result)))
}
func ReplaceIfMatch(text string, pattern string, replacement string) string {
re := regexp.MustCompile(fmt.Sprintf(`%s(\b)`, pattern))
if re.MatchString(text) {
text = re.ReplaceAllString(text, replacement)
}
return text
}
func GetCacheKey(provider string, language string, sEnc string) string {
return fmt.Sprintf("%s-%s-%s", provider, language, sEnc)
}
func GetPodListByLabels(client k.Interface,
namespace string,
labels map[string]string) (*v1.PodList, error) {
pods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{
LabelSelector: metav1.FormatLabelSelector(&metav1.LabelSelector{
MatchLabels: labels,
}),
})
if err != nil {
return nil, err
}
return pods, nil
}
func FileExists(path string) (bool, error) {
if _, err := os.Stat(path); err == nil {
return true, nil
} else if errors.Is(err, os.ErrNotExist) {
return false, nil
} else {
return false, err
}
}
func EnsureDirExists(dir string) error {
err := os.MkdirAll(dir, 0755)
if errors.Is(err, os.ErrExist) {
return nil
}
return err
}

View File

@@ -11,7 +11,8 @@
"README.md",
"deploy/manifest.yaml",
"chart/Chart.yaml",
"chart/values.yaml"
"chart/values.yaml",
"container/manifests/deployment.yaml"
],
"changelog-sections": [
{

View File

@@ -2,7 +2,14 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"helpers:pinGitHubActionDigests"
"helpers:pinGitHubActionDigests",
":gitSignOff"
],
"addLabels": ["dependencies"],
"postUpdateOptions": [
"gomodTidy",
"gomodMassage"
],
"packageRules": [
{
@@ -10,9 +17,31 @@
"matchCurrentVersion": "!/^0/",
"automerge": true
},
{
"matchManagers": ["gomod"],
"addLabels": ["go"]
},
{
"matchManagers": ["github-actions"],
"automerge": true
"addLabels": ["github_actions"]
},
{
"matchManagers": ["dockerfile"],
"addLabels": ["docker"]
}
],
"regexManagers": [
{
"fileMatch": [
"(^|\\/)Makefile$",
"(^|\\/)Dockerfile",
"(^|\\/).*\\.ya?ml$",
"(^|\\/).*\\.toml$",
"(^|\\/).*\\.sh$"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>.+?) depName=(?<depName>.+?)\\s.*?_VERSION ?(\\??=|\\: ?) ?\\\"?(?<currentValue>.+?)?\\\"?\\s"
]
}
]
}