Compare commits

..

58 Commits

Author SHA1 Message Date
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
43 changed files with 1826 additions and 253 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

@@ -25,7 +25,7 @@ jobs:
- name: Checkout
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
- uses: google-github-actions/release-please-action@ee9822ec2c397e8a364d634464339ac43a06e042 # v3
- uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3
id: release
with:
command: manifest
@@ -49,7 +49,7 @@ jobs:
with:
go-version: '1.20'
- name: Download Syft
uses: anchore/sbom-action/download-syft@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
uses: anchore/sbom-action/download-syft@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4
with:

View File

@@ -1,6 +1,12 @@
name: Run tests
on: [push]
on:
push:
branches:
- main
pull_request:
branches:
- main
env:
GO_VERSION: "~1.20"

View File

@@ -1 +1 @@
{".":"0.2.0"}
{".":"0.2.1"}

View File

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

View File

@@ -28,7 +28,7 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.0/k8sgpt_386.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_386.rpm
sudo rpm -ivh k8sgpt_386.rpm
```
<!---x-release-please-end-->
@@ -37,7 +37,7 @@ brew install k8sgpt
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.0/k8sgpt_amd64.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.rpm
sudo rpm -ivh -i k8sgpt_amd64.rpm
```
<!---x-release-please-end-->
@@ -49,7 +49,7 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.0/k8sgpt_386.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_386.deb
sudo dpkg -i k8sgpt_386.deb
```
<!---x-release-please-end-->
@@ -57,7 +57,7 @@ brew install k8sgpt
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.0/k8sgpt_amd64.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.deb
sudo dpkg -i k8sgpt_amd64.deb
```
<!---x-release-please-end-->
@@ -70,14 +70,14 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.0/k8sgpt_386.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/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.0/k8sgpt_amd64.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.apk
apk add k8sgpt_amd64.apk
```
<!---x-release-please-end-->x
@@ -140,6 +140,7 @@ you will be able to write your own analyzers.
- [x] serviceAnalyzer
- [x] eventAnalyzer
- [x] ingressAnalyzer
- [x] statefulSetAnalyzer
#### Optional
@@ -226,6 +227,40 @@ _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.**
## Upcoming major milestones
- [ ] Multiple AI backend support
@@ -254,4 +289,4 @@ Find us on [Slack](https://k8sgpt.slack.com/)
<a href="https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors">
<img src="https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt" />
</a>
</a>

View File

@@ -21,6 +21,7 @@ var (
language string
nocache bool
namespace string
anonymize bool
)
// AnalyzeCmd represents the problems command
@@ -66,7 +67,15 @@ var AnalyzeCmd = &cobra.Command{
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
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)
os.Exit(1)
}
// AnalysisResult configuration
config := &analysis.Analysis{
Namespace: namespace,
@@ -85,7 +94,7 @@ var AnalyzeCmd = &cobra.Command{
}
if explain {
err := config.GetAIResults(output)
err := config.GetAIResults(output, anonymize)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
@@ -113,6 +122,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

@@ -18,10 +18,9 @@ var addCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputFilters := strings.Split(args[0], ",")
coreFilters, additionalFilters := analyzer.ListFilters()
availableFilters := append(coreFilters, additionalFilters...)
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
// Verify filter exist
invalidFilters := []string{}
for _, f := range inputFilters {

View File

@@ -16,23 +16,35 @@ var listCmd = &cobra.Command{
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 := analyzer.ListFilters()
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
availableFilters := append(coreFilters, additionalFilters...)
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
fmt.Printf(color.YellowString("Active: \n"))
for _, filter := range activeFilters {
fmt.Printf("> %s\n", color.GreenString(filter))
// 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 {
fmt.Printf("> %s\n", color.RedString(filter))
// 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))
}
}
}

View File

@@ -21,7 +21,7 @@ var removeCmd = &cobra.Command{
// Get defined active_filters
activeFilters := viper.GetStringSlice("active_filters")
coreFilters, _ := analyzer.ListFilters()
coreFilters, _, _ := analyzer.ListFilters()
if len(activeFilters) == 0 {
activeFilters = coreFilters

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

@@ -4,17 +4,14 @@ import (
"os"
"path/filepath"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"k8s.io/client-go/util/homedir"
"github.com/fatih/color"
"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 (
@@ -55,14 +52,10 @@ func init() {
rootCmd.AddCommand(analyze.AnalyzeCmd)
rootCmd.AddCommand(filters.FiltersCmd)
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.AddCommand(serve.ServeCmd)
rootCmd.AddCommand(integration.IntegrationCmd)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)")
rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.")
// 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")
viper.Set("rootCmd", rootCmd)
}
// initConfig reads in config file and ENV variables if set.
@@ -83,14 +76,8 @@ func initConfig() {
viper.SafeWriteConfig()
}
//Initialise the kubeconfig
kubernetesClient, err := kubernetes.NewClient(kubecontext, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
os.Exit(1)
}
viper.Set("kubernetesClient", kubernetesClient)
viper.Set("kubecontext", kubecontext)
viper.Set("kubeconfig", kubeconfig)
viper.AutomaticEnv() // read in environment variables that match

View File

@@ -1,54 +0,0 @@
package serve
import (
"io/ioutil"
"log"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
port string
)
// generateCmd represents the auth command
var ServeCmd = &cobra.Command{
Use: "serve",
Short: "",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
rootCmd := viper.Get("rootCmd").(*cobra.Command)
// Start http server
router := httprouter.New()
router.GET("/:command", func(w http.ResponseWriter,
r *http.Request, p httprouter.Params) {
// find the command
command := p.ByName("command")
cmd, string, err := rootCmd.Find([]string{command})
if err != nil {
w.Write([]byte(err.Error()))
}
old := os.Stdout // keep backup of the real stdout
rd, d, _ := os.Pipe()
os.Stdout = d
cmd.Run(cmd, string)
d.Close()
out, _ := ioutil.ReadAll(rd)
os.Stdout = old // restoring the real stdout
w.Write(out)
})
log.Fatal(http.ListenAndServe(":8080", router))
},
}
func init() {
// add flag for backend
ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to serve on")
}

View File

@@ -1,8 +1,6 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
@@ -12,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) {
fmt.Printf("k8sgpt version %s", version)
cmd.Printf("k8sgpt version %s", version)
},
}

106
go.mod
View File

@@ -3,74 +3,172 @@ 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/magiconair/properties v1.8.7
github.com/sashabaranov/go-openai v1.6.1
github.com/mittwald/go-helm-client v0.12.1
github.com/sashabaranov/go-openai v1.7.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/term v0.7.0
helm.sh/helm/v3 v3.11.2
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/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.6.19 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // 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.1+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 v4.12.0+incompatible // 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-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-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.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/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/hcl v1.0.0 // 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/julienschmidt/httprouter v1.3.0 // 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.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/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/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.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.5.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.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.8.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/genproto v0.0.0-20230124163310-31e0e69b6fc2 // 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/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.1.1
// 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

614
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,6 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
}
func (a *OpenAIClient) 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))
@@ -77,7 +76,6 @@ func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool)
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", err
}

View File

@@ -3,14 +3,16 @@ package analysis
import (
"context"
"encoding/json"
"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"
)
@@ -20,7 +22,7 @@ type Analysis struct {
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []analyzer.Result
Results []common.Result
Namespace string
NoCache bool
Explain bool
@@ -34,9 +36,9 @@ const (
)
type JsonOutput struct {
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []analyzer.Result `json:"results"`
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []common.Result `json:"results"`
}
func (a *Analysis) RunAnalysis() error {
@@ -45,7 +47,7 @@ func (a *Analysis) RunAnalysis() error {
analyzerMap := analyzer.GetAnalyzerMap()
analyzerConfig := analyzer.Analyzer{
analyzerConfig := common.Analyzer{
Client: a.Client,
Context: a.Context,
Namespace: a.Namespace,
@@ -73,6 +75,8 @@ func (a *Analysis) RunAnalysis() error {
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
@@ -124,13 +128,13 @@ func (a *Analysis) PrintOutput() {
fmt.Printf("%s %s(%s)\n", color.CyanString("%d", n),
color.YellowString(result.Name), color.CyanString(result.ParentObject))
for _, err := range result.Error {
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err))
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text))
}
fmt.Println(color.GreenString(result.Details + "\n"))
}
}
func (a *Analysis) GetAIResults(output string) error {
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
if len(a.Results) == 0 {
return nil
}
@@ -141,16 +145,40 @@ func (a *Analysis) GetAIResults(output string) error {
}
for index, analysis := range a.Results {
parsedText, err := a.AIClient.Parse(a.Context, analysis.Error, a.NoCache)
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") {
color.Red("Exhausted API quota. Please try again later")
os.Exit(1)
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)
}
color.Red("Error: %v", err)
continue
}
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)

View File

@@ -3,22 +3,23 @@ package analysis
import (
"encoding/json"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/stretchr/testify/require"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/stretchr/testify/require"
)
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{},
Results: []common.Result{},
Namespace: "default",
}
expected := JsonOutput{
Status: StateOK,
Problems: 0,
Results: []analyzer.Result{},
Results: []common.Result{},
}
gotJson, err := analysis.JsonOutput()
@@ -40,13 +41,18 @@ func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
func TestAnalysis_ProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{
Results: []common.Result{
{
"Deployment",
"test-deployment",
[]string{"test-problem"},
"test-solution",
"parent-resource"},
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
Namespace: "default",
}
@@ -54,12 +60,18 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 1,
Results: []analyzer.Result{
{"Deployment",
"test-deployment",
[]string{"test-problem"},
"test-solution",
"parent-resource"},
Results: []common.Result{
{
Kind: "Deployment",
Name: "test-deployment",
Error: []common.Failure{
{
Text: "test-problem",
Sensitive: []common.Sensitive{},
},
},
Details: "test-solution",
ParentObject: "parent-resource"},
},
}
@@ -82,13 +94,22 @@ func TestAnalysis_ProblemJsonOutput(t *testing.T) {
func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
analysis := Analysis{
Results: []analyzer.Result{
Results: []common.Result{
{
"Deployment",
"test-deployment",
[]string{"test-problem", "another-test-problem"},
"test-solution",
"parent-resource"},
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",
}
@@ -96,12 +117,22 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
expected := JsonOutput{
Status: StateProblemDetected,
Problems: 2,
Results: []analyzer.Result{
{"Deployment",
"test-deployment",
[]string{"test-problem", "another-test-problem"},
"test-solution",
"parent-resource"},
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"},
},
}

View File

@@ -1,10 +1,15 @@
package analyzer
type IAnalyzer interface {
Analyze(analysis Analyzer) ([]Result, error)
}
import (
"fmt"
"os"
var coreAnalyzerMap = map[string]IAnalyzer{
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
)
var coreAnalyzerMap = map[string]common.IAnalyzer{
"Pod": PodAnalyzer{},
"ReplicaSet": ReplicaSetAnalyzer{},
"PersistentVolumeClaim": PvcAnalyzer{},
@@ -13,12 +18,12 @@ var coreAnalyzerMap = map[string]IAnalyzer{
"StatefulSet": StatefulSetAnalyzer{},
}
var additionalAnalyzerMap = map[string]IAnalyzer{
var additionalAnalyzerMap = map[string]common.IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
}
func ListFilters() ([]string, []string) {
func ListFilters() ([]string, []string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
@@ -28,12 +33,28 @@ func ListFilters() ([]string, []string) {
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
}
return coreKeys, additionalKeys
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())
}
}
return coreKeys, additionalKeys, integrationAnalyzers
}
func GetAnalyzerMap() map[string]IAnalyzer {
func GetAnalyzerMap() map[string]common.IAnalyzer {
mergedMap := make(map[string]IAnalyzer)
mergedMap := make(map[string]common.IAnalyzer)
// add core analyzer
for key, value := range coreAnalyzerMap {
@@ -45,5 +66,23 @@ func GetAnalyzerMap() map[string]IAnalyzer {
mergedMap[key] = value
}
integrationProvider := integration.NewIntegration()
for _, i := range integrationProvider.List() {
b, err := integrationProvider.IsActivate(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
if b {
in, err := integrationProvider.Get(i)
if err != nil {
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
in.AddAnalyzer(&mergedMap)
}
}
return mergedMap
}

View File

@@ -2,6 +2,7 @@ package analyzer
import (
"context"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@@ -2,23 +2,25 @@ 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 Analyzer) ([]Result, error) {
func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, hpa := range list.Items {
var failures []string
var failures []common.Failure
// check ScaleTargetRef exist
scaleTargetRef := hpa.Spec.ScaleTargetRef
@@ -26,35 +28,46 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
switch scaleTargetRef.Kind {
case "Deployment":
_, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
_, 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(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
_, 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(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
_, 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(a.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
_, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
if err != nil {
scaleTargetRefNotFound = true
}
default:
failures = append(failures, fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which does not possible option.", scaleTargetRef.Kind))
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, fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name))
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)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
HorizontalPodAutoscalers: hpa,
FailureDetails: failures,
}
@@ -63,7 +76,7 @@ func (HpaAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "HorizontalPodAutoscaler",
Name: key,
Error: value.FailureDetails,

View File

@@ -5,6 +5,7 @@ import (
"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"
@@ -23,7 +24,7 @@ func TestHPAAnalyzer(t *testing.T) {
},
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -55,7 +56,7 @@ func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
},
)
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -86,7 +87,7 @@ func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -101,7 +102,7 @@ func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err, "does not possible option.") {
if strings.Contains(err.Text, "which is not an option.") {
errorFound = true
break
}
@@ -133,7 +134,7 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
})
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -148,7 +149,7 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err, "does not exist.") {
if strings.Contains(err.Text, "does not exist.") {
errorFound = true
break
}
@@ -188,7 +189,7 @@ func TestHPAAnalyzerWithExistingScaleTargetRef(t *testing.T) {
)
hpaAnalyzer := HpaAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -2,30 +2,44 @@ 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 Analyzer) ([]Result, error) {
func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ing := range list.Items {
var failures []string
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, fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name))
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
}
@@ -35,7 +49,15 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if ingressClassName != nil {
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
if err != nil {
failures = append(failures, fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName))
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),
},
},
})
}
}
@@ -45,7 +67,19 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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, fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name))
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),
},
},
})
}
}
}
@@ -53,11 +87,23 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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, fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName))
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)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
Ingress: ing,
FailureDetails: failures,
}
@@ -66,7 +112,7 @@ func (IngressAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Ingress",
Name: key,
Error: value.FailureDetails,

View File

@@ -5,6 +5,7 @@ import (
"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"
@@ -23,7 +24,7 @@ func TestIngressAnalyzer(t *testing.T) {
})
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -56,7 +57,7 @@ func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
)
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -83,7 +84,7 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
})
ingressAnalyzer := IngressAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -99,7 +100,7 @@ func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err, "does not specify an Ingress class") {
if strings.Contains(err.Text, "does not specify an Ingress class") {
errorFound = true
break
}

View File

@@ -2,23 +2,25 @@ 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 Analyzer) ([]Result, error) {
func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pdb := range list.Items {
var failures []string
var failures []common.Failure
evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name)
if err != nil || evt == nil {
@@ -28,18 +30,36 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
if evt.Reason == "NoPods" && evt.Message != "" {
if pdb.Spec.Selector != nil {
for k, v := range pdb.Spec.Selector.MatchLabels {
failures = append(failures, fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v))
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, fmt.Sprintf("%s, expected expression %s", evt.Message, v))
failures = append(failures, common.Failure{
Text: fmt.Sprintf("%s, expected expression %s", evt.Message, v),
Sensitive: []common.Sensitive{},
})
}
} else {
failures = append(failures, fmt.Sprintf("%s, selector is nil", evt.Message))
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)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = common.PreAnalysis{
PodDisruptionBudget: pdb,
FailureDetails: failures,
}
@@ -47,7 +67,7 @@ func (PdbAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "PodDisruptionBudget",
Name: key,
Error: value.FailureDetails,

View File

@@ -2,6 +2,8 @@ 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"
)
@@ -9,16 +11,16 @@ import (
type PodAnalyzer struct {
}
func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]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" {
@@ -26,7 +28,10 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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{},
})
}
}
}
@@ -37,7 +42,10 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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
@@ -49,13 +57,16 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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,
}
@@ -63,7 +74,7 @@ func (PodAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Pod",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,6 +4,7 @@ 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"
@@ -31,7 +32,7 @@ func TestPodAnalyzer(t *testing.T) {
},
})
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -39,7 +40,7 @@ func TestPodAnalyzer(t *testing.T) {
Namespace: "default",
}
podAnalyzer := PodAnalyzer{}
var analysisResults []Result
var analysisResults []common.Result
analysisResults, err := podAnalyzer.Analyze(config)
if err != nil {
t.Error(err)

View File

@@ -2,13 +2,15 @@ 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 Analyzer) ([]Result, error) {
func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -16,10 +18,10 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, pvc := range list.Items {
var failures []string
var failures []common.Failure
// Check for empty rs
if pvc.Status.Phase == "Pending" {
@@ -30,11 +32,14 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
continue
}
if evt.Reason == "ProvisioningFailed" && 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", pvc.Namespace, pvc.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
PersistentVolumeClaim: pvc,
FailureDetails: failures,
}
@@ -42,7 +47,7 @@ func (PvcAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "PersistentVolumeClaim",
Name: key,
Error: value.FailureDetails,

View File

@@ -2,13 +2,15 @@ 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 Analyzer) ([]Result, error) {
func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -16,10 +18,10 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, rs := range list.Items {
var failures []string
var failures []common.Failure
// Check for empty rs
if rs.Status.Replicas == 0 {
@@ -27,12 +29,16 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
// 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}
failures = append(failures, common.Failure{
Text: rsStatus.Message,
Sensitive: []common.Sensitive{},
})
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
ReplicaSet: rs,
FailureDetails: failures,
}
@@ -40,7 +46,7 @@ func (ReplicaSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "ReplicaSet",
Name: key,
Error: value.FailureDetails,

View File

@@ -2,14 +2,16 @@ 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 Analyzer) ([]Result, error) {
func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
@@ -17,10 +19,10 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, ep := range list.Items {
var failures []string
var failures []common.Failure
// Check for empty service
if len(ep.Subsets) == 0 {
@@ -31,7 +33,19 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for k, v := range svc.Spec.Selector {
failures = append(failures, fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v))
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
@@ -44,13 +58,16 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
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))
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)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = common.PreAnalysis{
Endpoint: ep,
FailureDetails: failures,
}
@@ -58,7 +75,7 @@ func (ServiceAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "Service",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,6 +4,7 @@ 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"
@@ -32,7 +33,7 @@ func TestServiceAnalyzer(t *testing.T) {
},
}})
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},

View File

@@ -3,30 +3,61 @@ 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 Analyzer) ([]Result, error) {
func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
if err != nil {
return nil, err
}
var preAnalysis = map[string]PreAnalysis{}
var preAnalysis = map[string]common.PreAnalysis{}
for _, sts := range list.Items {
var failures []string
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, fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName))
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)] = PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = common.PreAnalysis{
StatefulSet: sts,
FailureDetails: failures,
}
@@ -34,7 +65,7 @@ func (StatefulSetAnalyzer) Analyze(a Analyzer) ([]Result, error) {
}
for key, value := range preAnalysis {
var currentAnalysis = Result{
var currentAnalysis = common.Result{
Kind: "StatefulSet",
Name: key,
Error: value.FailureDetails,

View File

@@ -4,9 +4,12 @@ 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"
)
@@ -21,7 +24,7 @@ func TestStatefulSetAnalyzer(t *testing.T) {
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -48,7 +51,7 @@ func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
})
statefulSetAnalyzer := StatefulSetAnalyzer{}
config := Analyzer{
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
@@ -64,7 +67,7 @@ func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
for _, analysis := range analysisResults {
for _, got := range analysis.Error {
if want == got {
if want == got.Text {
errorFound = true
}
}
@@ -76,3 +79,69 @@ func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
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)
}
}

View File

@@ -1,7 +1,9 @@
package analyzer
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"
@@ -11,6 +13,10 @@ import (
policyv1 "k8s.io/api/policy/v1"
)
type IAnalyzer interface {
Analyze(analysis Analyzer) ([]Result, error)
}
type Analyzer struct {
Client *kubernetes.Client
Context context.Context
@@ -22,7 +28,7 @@ type Analyzer struct {
type PreAnalysis struct {
Pod v1.Pod
FailureDetails []string
FailureDetails []Failure
ReplicaSet appsv1.ReplicaSet
PersistentVolumeClaim v1.PersistentVolumeClaim
Endpoint v1.Endpoints
@@ -30,12 +36,24 @@ type PreAnalysis struct {
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
PodDisruptionBudget policyv1.PodDisruptionBudget
StatefulSet appsv1.StatefulSet
// Integrations
TrivyVulnerabilityReport trivy.VulnerabilityReport
}
type Result struct {
Kind string `json:"kind"`
Name string `json:"name"`
Error []string `json:"error"`
Details string `json:"details"`
ParentObject string `json:"parentObject"`
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,113 @@
package integration
import (
"errors"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
"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")
}
if err := integrations[name].Deploy(namespace); err != nil {
return err
}
// Update filters
activeFilters := viper.GetStringSlice("active_filters")
activeFilters = append(activeFilters, integrations[name].GetAnalyzerName())
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) Deactivate(name string, namespace string) error {
if _, ok := integrations[name]; !ok {
return errors.New("integration not found")
}
if err := integrations[name].UnDeploy(namespace); err != nil {
return err
}
// Update filters
// This might be a bad idea, but we cannot reference analyzer here
activeFilters := viper.GetStringSlice("active_filters")
// Remove filter
for i, v := range activeFilters {
if v == integrations[name].GetAnalyzerName() {
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
break
}
}
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,18 +1,31 @@
package kubernetes
import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/scheme"
)
type Client struct {
Client kubernetes.Interface
Client kubernetes.Interface
RestClient rest.Interface
Config *rest.Config
}
func (c *Client) GetConfig() *rest.Config {
return c.Config
}
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) {
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
@@ -29,8 +42,18 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
if err != nil {
return nil, err
}
c.APIPath = "/api"
c.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
c.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
restClient, err := rest.RESTClientFor(c)
if err != nil {
return nil, err
}
return &Client{
Client: clientSet,
Client: clientSet,
RestClient: restClient,
Config: c,
}, nil
}

View File

@@ -2,11 +2,26 @@ package util
import (
"context"
"encoding/base64"
"fmt"
"math/rand"
"regexp"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
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 {
@@ -96,3 +111,21 @@ func SliceDiff(source, dest []string) []string {
}
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
}