Compare commits

...

240 Commits

Author SHA1 Message Date
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
78 changed files with 5496 additions and 733 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-approvers

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@ee9822ec2c397e8a364d634464339ac43a06e042 # 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 ./...

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.3"}
{".":"0.2.3"}

View File

@@ -1,5 +1,283 @@
# Changelog
## [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)

16
Makefile Normal file
View File

@@ -0,0 +1,16 @@
.PHONY: docker-build
IMG ?= ghcr.io/k8sgpt-ai/k8sgpt:latest
deploy:
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
undeploy:
kubectl delete secret ai-backend-secret --namespace=k8sgpt
kubectl delete -f container/manifests
kubectl delete ns k8sgpt
docker-build:
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

194
README.md
View File

@@ -2,27 +2,94 @@
<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.
## Installation
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
### Linux/Mac via brew
# 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.3/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.3/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.3/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.3/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.3/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.3/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 bottle and must be
built from source. k8sgpt Install Clang or run brew install gcc.
==> 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.
@@ -32,21 +99,27 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
```
</details>
### Windows
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
## 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
## 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
* 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.
@@ -54,17 +127,29 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
## Analyzers
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
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
@@ -76,6 +161,7 @@ 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
version Print the version number of k8sgpt
@@ -90,6 +176,36 @@ Flags:
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_
```
@@ -115,6 +231,42 @@ _Output to JSON_
k8sgpt analyze --explain --filter=Service --output=json
```
_Anonymize during explain_
```
k8sgpt analyze --explain --filter=Service --output=json --anonymize
```
### How does anonymization work?
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.
<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>
## Upcoming major milestones
- [ ] Multiple AI backend support
@@ -123,20 +275,32 @@ k8sgpt analyze --explain --filter=Service --output=json
## What about kubectl-ai?
The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to 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.
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
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.
## Configuration
`k8sgpt` stores config data in `~/.k8sgpt.yaml` the data is stored in plain text, including your OpenAI key.
`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 |
## Contributing
Please read our [contributing guide](./CONTRIBUTING.md).
## Community
* Find us on [Slack](https://k8sgpt.slack.com/)
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>

View File

@@ -1,19 +1,12 @@
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 (
@@ -24,6 +17,7 @@ var (
language string
nocache bool
namespace string
anonymize bool
)
// AnalyzeCmd represents the problems command
@@ -35,106 +29,34 @@ 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)
// Analysis configuration
config := &analyzer.AnalysisConfiguration{
Namespace: namespace,
NoCache: nocache,
Explain: explain,
}
var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{}
if err := analyzer.RunAnalysis(ctx, filters, config, client,
aiClient, analysisResults); err != nil {
// AnalysisResult configuration
config, err := analysis.NewAnalysis(backend, language, filters, namespace, nocache, explain)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
var bar *progressbar.ProgressBar
if len(*analysisResults) > 0 {
bar = progressbar.Default(int64(len(*analysisResults)))
} else {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
err = config.RunAnalysis()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// 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, config, aiClient, analysis.Error)
if err != nil {
// Check for exhaustion
if strings.Contains(err.Error(), "status code: 429") {
color.Red("Exhausted API quota. Please try again later")
os.Exit(1)
}
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)\n", color.CyanString("%d", n),
color.YellowString(analysis.Name), color.CyanString(analysis.ParentObject))
for _, err := range analysis.Error {
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err))
}
color.GreenString(analysis.Details)
}
output, err := config.PrintOutput(output)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(output))
},
}
@@ -144,6 +66,8 @@ func init() {
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 analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)")
// explain flag

View File

@@ -7,13 +7,16 @@ 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
model string
)
// authCmd represents the auth command
@@ -23,30 +26,65 @@ 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,
}
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 +96,8 @@ 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")
}

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

@@ -0,0 +1,72 @@
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, ", "))
},
}

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

@@ -0,0 +1,25 @@
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)
}

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

@@ -0,0 +1,52 @@
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))
}
}
}
},
}

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

@@ -0,0 +1,74 @@
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

@@ -7,7 +7,6 @@ import (
"github.com/spf13/viper"
"os/exec"
"runtime"
"time"
)
var (
@@ -31,10 +30,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 +41,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 +57,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,33 @@
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,35 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
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,28 @@
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")
}

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

@@ -0,0 +1,50 @@
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,28 @@
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 +46,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.AddCommand(integration.IntegrationCmd)
rootCmd.AddCommand(serve.ServeCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.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.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 +71,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 +92,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
}

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

@@ -0,0 +1,91 @@
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")
// If the envs are set, alocate in place to the aiProvider
// else exit with error
if backend != "" || password != "" || model != "" {
aiProvider = &ai.AIProvider{
Name: backend,
Password: password,
Model: model,
}
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

@@ -10,7 +10,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,4 @@
FROM golang:1.20.2-alpine3.16 AS builder
FROM golang:1.20.3-alpine3.16 AS builder
ENV CGO_ENABLED=0

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.3 #x-release-please-version
ports:
- containerPort: 8080
args: ["serve"]
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.5"
memory: "256Mi"
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,13 @@
apiVersion: v1
kind: Service
metadata:
name: k8sgpt-service
namespace: k8sgpt
spec:
selector:
app: k8sgpt
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP

11
demo Executable file
View File

@@ -0,0 +1,11 @@
. demo-magic.sh
clear
pe "k8sgpt filter list"
pe "k8sgpt analyze --filter=Pod --explain -o json | jq ."
pe "k8sgpt integration list"
pe "k8sgpt integration activate trivy"
pe "k8sgpt filter list"
pe "k8sgpt analyze --filter=VulnerabilityReport"
pe "./k8sgpt analyze --filter=Node --explain"

168
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.0
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.8.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 // indirect
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.85.0 // indirect
github.com/aquasecurity/go-dep-parser v0.0.0-20230324043952-2172dc218241 // indirect
github.com/aquasecurity/table v1.8.0 // indirect
github.com/aquasecurity/tml v0.6.1 // indirect
github.com/aquasecurity/trivy v0.39.0 // indirect
github.com/aquasecurity/trivy-db v0.0.0-20230116084806-4bcdf1c414d0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // 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.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.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
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.10.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20221109205753-fc8884afc316 // 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
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.53.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

773
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

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,33 @@
package ai
import "context"
import (
"context"
)
type IAI interface {
Configure(token string, language string) error
Configure(token string, model string, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
Parse(ctx context.Context, prompt []string, nocache bool) (string, error)
GetName() 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"`
}

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

@@ -0,0 +1,57 @@
package ai
import (
"context"
"encoding/base64"
"fmt"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/spf13/viper"
"strings"
)
type NoOpAIClient struct {
client string
language string
model string
}
func (c *NoOpAIClient) Configure(token string, model string, language string) error {
c.language = language
c.client = fmt.Sprintf("I am a noop client with the token %s ", token)
c.model = model
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, 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))
cacheKey := util.GetCacheKey(a.GetName(), sEnc)
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}
if !viper.IsSet(cacheKey) {
viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
}
func (a *NoOpAIClient) GetName() string {
return "noopai"
}

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

@@ -0,0 +1,96 @@
package ai
import (
"context"
"encoding/base64"
"errors"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"strings"
"github.com/fatih/color"
"github.com/spf13/viper"
"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
model string
}
func (c *OpenAIClient) Configure(token string, model string, language string) error {
client := openai.NewClient(token)
if client == nil {
return errors.New("error creating OpenAI client")
}
c.language = language
c.client = client
c.model = model
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, nocache bool) (string, error) {
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
cacheKey := util.GetCacheKey(a.GetName(), sEnc)
// find in viper cache
if viper.IsSet(cacheKey) && !nocache {
// retrieve data from cache
response := viper.GetString(cacheKey)
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
}
if !viper.IsSet(cacheKey) || nocache {
viper.Set(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
}
func (a *OpenAIClient) GetName() string {
return "openai"
}

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

@@ -0,0 +1,203 @@
package analysis
import (
"context"
"errors"
"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/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
NoCache bool
Explain bool
}
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) (*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.Password, aiProvider.Model, 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,
NoCache: noCache,
Explain: explain,
}, 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,
}
// if there are no filters selected and no active_filters then run all of them
if len(a.Filters) == 0 && len(activeFilters) == 0 {
for _, analyzer := range analyzerMap {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
return err
}
a.Results = append(a.Results, results...)
}
return nil
}
// if the filters flag is specified
if len(a.Filters) != 0 {
for _, filter := range a.Filters {
if analyzer, ok := analyzerMap[filter]; ok {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
return err
}
a.Results = append(a.Results, results...)
} else {
return errors.New(fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter))
}
}
return nil
}
// use active_filters
for _, filter := range activeFilters {
if analyzer, ok := analyzerMap[filter]; ok {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
return err
}
a.Results = append(a.Results, results...)
}
}
return nil
}
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.NoCache)
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,154 @@
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,30 +0,0 @@
package analyzer
import (
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
)
type AnalysisConfiguration struct {
Namespace string
NoCache bool
Explain bool
}
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
Ingress networkingv1.Ingress
}
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,83 +1,101 @@
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"
)
var analyzerMap = map[string]func(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error{
"Pod": AnalyzePod,
"ReplicaSet": AnalyzeReplicaSet,
"PersistentVolumeClaim": AnalyzePersistentVolumeClaim,
"Service": AnalyzeEndpoints,
"Ingress": AnalyzeIngress,
var (
AnalyzerErrorsMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "analyzer_errors",
Help: "Number of errors detected by analyzer",
}, []string{"analyzer_name", "object_name", "namespace"})
)
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 RunAnalysis(ctx context.Context, filters []string, config *AnalysisConfiguration,
client *kubernetes.Client,
aiClient ai.IAI, analysisResults *[]Analysis) error {
// if there are no filters selected then run all of them
if len(filters) == 0 {
for _, analyzer := range analyzerMap {
if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
return nil
}
for _, filter := range filters {
if analyzer, ok := analyzerMap[filter]; ok {
if err := analyzer(ctx, config, client, aiClient, analysisResults); err != nil {
return err
}
}
}
return nil
var additionalAnalyzerMap = map[string]common.IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
"NetworkPolicy": NetworkPolicyAnalyzer{},
}
func ParseViaAI(ctx context.Context, config *AnalysisConfiguration,
aiClient ai.IAI, prompt []string) (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) && !config.NoCache {
// retrieve data from cache
response := viper.GetString(sEnc)
if response == "" {
color.Red("error retrieving cached data")
return "", nil
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 "", err
}
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
}

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

@@ -0,0 +1,119 @@
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("").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,126 @@
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")
}

View File

@@ -0,0 +1,69 @@
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("").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,62 @@
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")
}

View File

@@ -8,36 +8,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 {

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

@@ -0,0 +1,98 @@
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 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
scaleTargetRefNotFound := false
switch scaleTargetRef.Kind {
case "Deployment":
_, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicationController":
_, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "ReplicaSet":
_, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
case "StatefulSet":
_, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
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 scaleTargetRefNotFound {
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),
},
},
})
}
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
}

View File

@@ -0,0 +1,206 @@
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"
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 TestHPAAnalyzerWithExistingScaleTargetRef(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{},
},
},
)
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)
}
}

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

@@ -0,0 +1,135 @@
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

@@ -1,64 +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 AnalyzeIngress(ctx context.Context, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
list, err := client.GetClient().NetworkingV1().Ingresses(config.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
var preAnalysis = map[string]PreAnalysis{}
for _, ing := range list.Items {
var failures []string
// loop over rules
for _, rule := range ing.Spec.Rules {
// loop over paths
for _, path := range rule.HTTP.Paths {
_, err := client.GetClient().CoreV1().Services(ing.Namespace).Get(ctx, path.Backend.Service.Name, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name))
}
}
}
for _, tls := range ing.Spec.TLS {
_, err := client.GetClient().CoreV1().Secrets(ing.Namespace).Get(ctx, tls.SecretName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName))
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
}
}
for key, value := range preAnalysis {
var currentAnalysis = Analysis{
Kind: "Ingress",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Ingress.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
}
return nil
}

View File

@@ -0,0 +1,115 @@
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")
}
}

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

@@ -0,0 +1,83 @@
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
}

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

@@ -0,0 +1,122 @@
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)
}

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

@@ -0,0 +1,83 @@
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
}

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

@@ -0,0 +1,111 @@
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)
}

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

@@ -0,0 +1,89 @@
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
}

View File

@@ -1,27 +1,33 @@
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/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
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 := client.GetClient().CoreV1().Pods(config.Namespace).List(ctx, metav1.ListOptions{})
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return err
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pod := range list.Items {
var failures []string
var failures []common.Failure
// Check for pending pods
if pod.Status.Phase == "Pending" {
@@ -29,7 +35,10 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
for _, containerStatus := range pod.Status.Conditions {
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
if containerStatus.Message != "" {
failures = []string{containerStatus.Message}
failures = append(failures, common.Failure{
Text: containerStatus.Message,
Sensitive: []common.Sensitive{},
})
}
}
}
@@ -40,42 +49,49 @@ func AnalyzePod(ctx context.Context, config *AnalysisConfiguration,
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)
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 := FetchLatestPodEvent(ctx, client, &pod)
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, 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)] = PreAnalysis{
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 = Analysis{
Kind: "Pod",
var currentAnalysis = common.Result{
Kind: kind,
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(client, value.Pod.ObjectMeta)
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent
*analysisResults = append(*analysisResults, currentAnalysis)
a.Results = append(a.Results, currentAnalysis)
}
return nil
return a.Results, nil
}

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

@@ -0,0 +1,49 @@
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.",
},
},
},
})
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)
}

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

@@ -0,0 +1,69 @@
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, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().PersistentVolumeClaims(config.Namespace).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
}

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

@@ -0,0 +1,67 @@
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,58 +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, config *AnalysisConfiguration,
client *kubernetes.Client, aiClient ai.IAI, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().AppsV1().ReplicaSets(config.Namespace).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
}

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

@@ -0,0 +1,96 @@
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,75 +0,0 @@
package analyzer
import (
"context"
"fmt"
"github.com/fatih/color"
"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, config *AnalysisConfiguration, client *kubernetes.Client, aiClient ai.IAI,
analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := client.GetClient().CoreV1().Endpoints(config.Namespace).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 {
color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name)
continue
}
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,50 @@
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)
}

View File

@@ -0,0 +1,88 @@
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,147 @@
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)
}
}

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

@@ -0,0 +1,62 @@
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,129 @@
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,74 @@
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,103 @@
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,43 @@
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 +46,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
}

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

@@ -0,0 +1,113 @@
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
}
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")
s.Output = r.URL.Query().Get("output")
if s.Output == "" {
s.Output = "json"
}
config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain)
if err != nil {
health.Failure++
fmt.Fprintf(w, err.Error())
}
err = config.RunAnalysis()
if err != nil {
color.Red("Error: %v", err)
health.Failure++
fmt.Fprintf(w, err.Error())
}
if explain {
err := config.GetAIResults(s.Output, anonymize)
if err != nil {
color.Red("Error: %v", err)
health.Failure++
fmt.Fprintf(w, err.Error())
}
}
out, err := config.PrintOutput(s.Output)
if err != nil {
color.Red("Error: %v", err)
health.Failure++
fmt.Fprintf(w, err.Error())
}
health.Success++
fmt.Fprintf(w, string(out))
}
func (s *Config) Serve() error {
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, nil)
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

@@ -2,11 +2,30 @@ 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 {
@@ -65,3 +84,91 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
}
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, sEnc string) string {
return fmt.Sprintf("%s-%s", provider, 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.Mkdir(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"
]
}
]
}