Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Jones
aada4b2f2d chore: Update README.md 2023-03-28 11:49:36 +01:00
80 changed files with 645 additions and 5712 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/k8sgpt-approvers
* @k8sgpt-ai/maintainers

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
- uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3
- uses: google-github-actions/release-please-action@e0b9d1885d92e9a93d5ce8656de60e3b806e542c # v3
id: release
with:
command: manifest
@@ -36,20 +36,19 @@ 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # 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:
@@ -71,10 +70,9 @@ 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@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
with:
submodules: recursive
@@ -100,11 +98,11 @@ jobs:
${{ env.IMAGE_TAG }}
builder: ${{ steps.buildx.outputs.name }}
push: true
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
- name: Generate SBOM
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
uses: anchore/sbom-action@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
with:
image: ${{ env.IMAGE_TAG }}
artifact-name: sbom-${{ env.IMAGE_NAME }}

View File

@@ -1,27 +0,0 @@
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,27 +16,6 @@ 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.2.2"}
{".":"0.1.0"}

View File

@@ -1,332 +1,5 @@
# Changelog
## [0.2.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.1...v0.2.2) (2023-04-14)
### Features
* add simple health endpoint ([26c0cb2](https://github.com/k8sgpt-ai/k8sgpt/commit/26c0cb2eed75695220007e6d6f7b492c2641a149))
* anoymization based on pr feedback ([19e1b94](https://github.com/k8sgpt-ai/k8sgpt/commit/19e1b94e7c9ce4092f1dabd659023a193b2c4a92))
* anoymization based on pr feedback ([fe52951](https://github.com/k8sgpt-ai/k8sgpt/commit/fe529510b68ac5fbd39c147c7719abe2e7d20894))
* check for auth only in case of --explain ([57790e5](https://github.com/k8sgpt-ai/k8sgpt/commit/57790e5bc7037f57a4f73248fe05cac192511470))
* first version of serve ([b2e8add](https://github.com/k8sgpt-ai/k8sgpt/commit/b2e8adda333fbd508f0f01f2afcabc57bf9948c2))
* unified cmd and api ([9157d4d](https://github.com/k8sgpt-ai/k8sgpt/commit/9157d4dd1312bf75b336beb0e097422b303d22f1))
* updated api ([adae2ef](https://github.com/k8sgpt-ai/k8sgpt/commit/adae2ef71d81431711c552159362336e496b21ee))
### Bug Fixes
* bool conversion ([336ec2a](https://github.com/k8sgpt-ai/k8sgpt/commit/336ec2a42693d0df325b95cbebd9545b19e27725))
* **deps:** update module helm.sh/helm/v3 to v3.11.3 ([4dd91ed](https://github.com/k8sgpt-ai/k8sgpt/commit/4dd91ed8263292476054bc70d3d6a3149f88f1b3))
* naming ([159b385](https://github.com/k8sgpt-ai/k8sgpt/commit/159b3851ec54e93a447b0f13aa4ceb7b8b8f62db))
* start message ([6b63027](https://github.com/k8sgpt-ai/k8sgpt/commit/6b630275eb64b799c50e3074cb22a3b41bb893de))
### Docs
* fix Slack link ([1dccaea](https://github.com/k8sgpt-ai/k8sgpt/commit/1dccaea3f4f96b2da52999eed5031f02a89c0b6e))
### Other
* added oidc ([bffad41](https://github.com/k8sgpt-ai/k8sgpt/commit/bffad41134d231b16f136a619174ff3bee61765a))
* additional analyzers ([23071fd](https://github.com/k8sgpt-ai/k8sgpt/commit/23071fd2e6b421f0f5fcd6e7e4985c6900e5405c))
* **deps:** bump github.com/docker/docker ([#268](https://github.com/k8sgpt-ai/k8sgpt/issues/268)) ([7d1e2ac](https://github.com/k8sgpt-ai/k8sgpt/commit/7d1e2acaf3eaf00929ff43b9373df6a4be100795))
* **deps:** update actions/checkout digest to 83b7061 ([cbe6f27](https://github.com/k8sgpt-ai/k8sgpt/commit/cbe6f27c05e82f55f41b648b01972ba2c43f1534))
* **deps:** update actions/checkout digest to 8e5e7e5 ([#266](https://github.com/k8sgpt-ai/k8sgpt/issues/266)) ([0af34a1](https://github.com/k8sgpt-ai/k8sgpt/commit/0af34a1a95502dc26d7e08bac896f691e4969090))
* **deps:** update module oras.land/oras-go to v1.2.3 ([#249](https://github.com/k8sgpt-ai/k8sgpt/issues/249)) ([13c9231](https://github.com/k8sgpt-ai/k8sgpt/commit/13c9231aafef3a259fd678a80063ad2e968d6e95))
* fixing up tests ([f9b25d9](https://github.com/k8sgpt-ai/k8sgpt/commit/f9b25d9e85a8faaf1aae59d7bedc4c0f3538181e))
* fixing up tests ([498d454](https://github.com/k8sgpt-ai/k8sgpt/commit/498d454c174c7d39da1ca63b2a201e797d7e5e1c))
* Merge branch 'main' into feat/additional-analyzers ([4d36248](https://github.com/k8sgpt-ai/k8sgpt/commit/4d3624830ff840f9ccf11d7da20953bdf4c7c7fc))
* removing field ([ddb51c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ddb51c7af470044a8514ed013b44cc135e4c0f10))
## [0.2.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.0...v0.2.1) (2023-04-12)
### Features
* add anonymization example to README ([8a60b57](https://github.com/k8sgpt-ai/k8sgpt/commit/8a60b579409c67f092156ba1adf1be22cce37b8c))
* add anonymization flag ([d2a84ea](https://github.com/k8sgpt-ai/k8sgpt/commit/d2a84ea2b5c800dd900aac3a48b1914bd9ddb917))
* add more details on anonymize flag ([b687473](https://github.com/k8sgpt-ai/k8sgpt/commit/b687473e6169406002b0ee8be6ebb9ce43b46495))
* add storage class names' check. ([c8ba7d6](https://github.com/k8sgpt-ai/k8sgpt/commit/c8ba7d62d2f1d262263d1dff8f980e91cdcd50e8))
* improve documentation ([6f08654](https://github.com/k8sgpt-ai/k8sgpt/commit/6f0865413fc2854450d217225199cec199972490))
* improve documentation & update hpa message ([11326c1](https://github.com/k8sgpt-ai/k8sgpt/commit/11326c1c5f307c718e8d1e56099537314ffedadd))
* improve security of the MaskString function ([08f2a89](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2a89e54a65544322814286977b2c05acce89d))
* initial impl of integration ([b0e5170](https://github.com/k8sgpt-ai/k8sgpt/commit/b0e517006e65ac2b4e2d4e2696531d4bbf62c34b))
* initial impl of integration ([61d6e52](https://github.com/k8sgpt-ai/k8sgpt/commit/61d6e524657272cf3a967c724f212677fcfe7d2b))
* integration ready for first review ([3682f5c](https://github.com/k8sgpt-ai/k8sgpt/commit/3682f5c7ebb9590e92162eed214a8127f71bcd81))
* introduce StatefulSet analyser. ([c041ce2](https://github.com/k8sgpt-ai/k8sgpt/commit/c041ce2bbb4ecbc6f5637207c9f3071eee022744))
* refactor integration to use Failure object ([c0afc0f](https://github.com/k8sgpt-ai/k8sgpt/commit/c0afc0f5c91cfa50b1f7af901800ff0a2b492d18))
* return errors if filter specified by flag does not exist. ([dd5824f](https://github.com/k8sgpt-ai/k8sgpt/commit/dd5824f4365b01e3c501d8b5cda914dff138e03d))
### Bug Fixes
* **deps:** update kubernetes packages to v0.27.0 ([7a97034](https://github.com/k8sgpt-ai/k8sgpt/commit/7a97034cf41cb265111c752ee3d54fd90524ef59))
* **deps:** update module github.com/sashabaranov/go-openai to v1.7.0 ([#227](https://github.com/k8sgpt-ai/k8sgpt/issues/227)) ([5f3a5a5](https://github.com/k8sgpt-ai/k8sgpt/commit/5f3a5a54a02967acce40f8b4e9dd3a154c83f58c))
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([fe261b3](https://github.com/k8sgpt-ai/k8sgpt/commit/fe261b375f4d7990906620f53ac26e792a34731b))
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([ab55f15](https://github.com/k8sgpt-ai/k8sgpt/commit/ab55f157ef026502d29eadf5ad83e917fe085a6c))
* improve ReplaceIfMatch regex ([fd936ce](https://github.com/k8sgpt-ai/k8sgpt/commit/fd936ceaf725d1c1ed1f53eaa2204455dcd1e2af))
* pdb test ([705d2a0](https://github.com/k8sgpt-ai/k8sgpt/commit/705d2a0dcebb63783782e06b6b775393daf1efb7))
* use hpa namespace instead analyzer namespace ([#230](https://github.com/k8sgpt-ai/k8sgpt/issues/230)) ([a582d44](https://github.com/k8sgpt-ai/k8sgpt/commit/a582d444c5c53f25d7172947c690b35cad2cc176))
### Docs
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([b45ff1a](https://github.com/k8sgpt-ai/k8sgpt/commit/b45ff1aa8ef447df2b74bb8c6225e2f3d7c5bd63))
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([ba01bd4](https://github.com/k8sgpt-ai/k8sgpt/commit/ba01bd4b6ecd64fbe249be54f20471afc6339208))
### Other
* add fakeai provider ([#218](https://github.com/k8sgpt-ai/k8sgpt/issues/218)) ([e449cb6](https://github.com/k8sgpt-ai/k8sgpt/commit/e449cb60230d440d5b8e00062db63de5d6d413bf))
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([db1388f](https://github.com/k8sgpt-ai/k8sgpt/commit/db1388fd20dcf21069adcecd2796f2e1231162c8))
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([992b107](https://github.com/k8sgpt-ai/k8sgpt/commit/992b107c2d906663bb22998004a0859bccd45c77))
* compiling successfully ([80ac51c](https://github.com/k8sgpt-ai/k8sgpt/commit/80ac51c804351226e1764e3e649ac56e22de3749))
* **deps:** update anchore/sbom-action action to v0.14.1 ([#228](https://github.com/k8sgpt-ai/k8sgpt/issues/228)) ([9423b53](https://github.com/k8sgpt-ai/k8sgpt/commit/9423b53c1dbae3d0762420a0bacbdace9a2c18c9))
* **deps:** update google-github-actions/release-please-action digest to c078ea3 ([a1d8012](https://github.com/k8sgpt-ai/k8sgpt/commit/a1d8012a5c748aee3f16621d6da9a0f0c8cba293))
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([55dda43](https://github.com/k8sgpt-ai/k8sgpt/commit/55dda432ab89c4917bd28fceabcbe5569c0bf530))
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([21dc61c](https://github.com/k8sgpt-ai/k8sgpt/commit/21dc61c04f4d772b5147b38a4d28e5dbddf5cdd8))
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([428c348](https://github.com/k8sgpt-ai/k8sgpt/commit/428c3485868a7be95ea6776694e30b36badf4b5c))
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([3845d47](https://github.com/k8sgpt-ai/k8sgpt/commit/3845d4747f4e0fc823d1bcf631d6ecdd5e4ccd03))
* Fixing broken tests ([c809af3](https://github.com/k8sgpt-ai/k8sgpt/commit/c809af3f47388599fda3a88a4638feae1dc90492))
* fixing filters ([258c69a](https://github.com/k8sgpt-ai/k8sgpt/commit/258c69a17c977867dfd0a7ad02727270b7c172e7))
* fixing filters ([4d20f70](https://github.com/k8sgpt-ai/k8sgpt/commit/4d20f70fb40ff326ceb279f699068ec4956a2f10))
* merged ([096321b](https://github.com/k8sgpt-ai/k8sgpt/commit/096321b31a6cf0d53b1861a3e4ad1efe84f697cc))
* updated analysis_test.go ([825e9a4](https://github.com/k8sgpt-ai/k8sgpt/commit/825e9a43bd3ab7aa3ea52f315993cd778ea039e3))
* updated link output ([1b7f4ce](https://github.com/k8sgpt-ai/k8sgpt/commit/1b7f4ce44a499e5389aec42fdee00bfa81ef0888))
* updating based on feedback ([5e5d4b6](https://github.com/k8sgpt-ai/k8sgpt/commit/5e5d4b6de160dc7533067e1c0d8403c3faac1a9f))
* weird new line after filter removed ([fabe01a](https://github.com/k8sgpt-ai/k8sgpt/commit/fabe01aa019f1db45ed2ff780f0d6d63297b230b))
## [0.2.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.8...v0.2.0) (2023-04-05)
### ⚠ BREAKING CHANGES
* The format of the configuration file has changed. Users must update their configuration files to use the new format.
### Features
* add support for new configuration format ([9b243cd](https://github.com/k8sgpt-ai/k8sgpt/commit/9b243cdcaab1742fca2516bc1ae5710505e0eb65))
* add tests for hpa analyzer ([5a59abb](https://github.com/k8sgpt-ai/k8sgpt/commit/5a59abb55d9e76f3095b903ea973138b1afdccf2))
* add tests for ingress analyzer && Use t.Fatalf to report a fatal error if RunAnalysis returns an unexpected error ([e27e940](https://github.com/k8sgpt-ai/k8sgpt/commit/e27e9409dcaad78eefd79659e91364617407ae59))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.6.1 ([#207](https://github.com/k8sgpt-ai/k8sgpt/issues/207)) ([eeac731](https://github.com/k8sgpt-ai/k8sgpt/commit/eeac731858999f6f462a7b6ccf210af603674b30))
* **deps:** update module github.com/spf13/cobra to v1.7.0 ([5d5e082](https://github.com/k8sgpt-ai/k8sgpt/commit/5d5e082f417905954be33b3a620efef674f2588d))
* **deps:** update module github.com/stretchr/testify to v1.8.2 ([f5e3ca0](https://github.com/k8sgpt-ai/k8sgpt/commit/f5e3ca0bcab9325145a2e1d8624f585ffee8e29f))
* **deps:** update module golang.org/x/term to v0.7.0 ([8ab3573](https://github.com/k8sgpt-ai/k8sgpt/commit/8ab3573e13a3e3ab98e6f0aa76e429117c888f7f))
* details in json output ([2f21002](https://github.com/k8sgpt-ai/k8sgpt/commit/2f2100289953af7820bbb01f2c980cf5492de079))
* fixed hpa tests after rebase ([a24d1f1](https://github.com/k8sgpt-ai/k8sgpt/commit/a24d1f1b304e9448e63c1b7fc283b4cc8bc639aa))
* regression on dynamic filters ([93bcc62](https://github.com/k8sgpt-ai/k8sgpt/commit/93bcc627ba64a9139e65290a8512e0a9b4bf1a69))
* Spelling ([ba4d701](https://github.com/k8sgpt-ai/k8sgpt/commit/ba4d7016814ce97353e98658d5bbcd692007e4a9))
### Docs
* add curl command and release-please annoations ([1849209](https://github.com/k8sgpt-ai/k8sgpt/commit/184920988f7da928cca7fae4a676e4ee5f13cad1))
* add guide to details block ([ddc120e](https://github.com/k8sgpt-ai/k8sgpt/commit/ddc120e7c2657385737d3490def28dbabdd2242d))
* add installation guide via packages ([8e4ce6a](https://github.com/k8sgpt-ai/k8sgpt/commit/8e4ce6a974813258fb9cbeabbcaa3b8f6966a748))
* minor change ([53c1330](https://github.com/k8sgpt-ai/k8sgpt/commit/53c13305383eb454fe45fefa3483cef4821d5d34))
* modify README ([fc47c58](https://github.com/k8sgpt-ai/k8sgpt/commit/fc47c58ae1c2b5511ebbe0ed35714e4ecbb4bb7a))
* modify README ([0f46ceb](https://github.com/k8sgpt-ai/k8sgpt/commit/0f46ceb4456a90e7e05aeff23d25d5775bbf9c2b))
### Other
* added initial tests for json output ([22e3166](https://github.com/k8sgpt-ai/k8sgpt/commit/22e31661bff27b28339898826a34ffdcfcff3583))
* analyzer and ai interfacing ([#200](https://github.com/k8sgpt-ai/k8sgpt/issues/200)) ([0195bfa](https://github.com/k8sgpt-ai/k8sgpt/commit/0195bfab30ab748b3bb7f1b8c8f0e988b99ee54d))
* **deps:** pin anchore/sbom-action action to 448520c ([#203](https://github.com/k8sgpt-ai/k8sgpt/issues/203)) ([9ff3fbc](https://github.com/k8sgpt-ai/k8sgpt/commit/9ff3fbc382ed78b55a7a1966ecdae186c03b2848))
* **deps:** update golang docker tag to v1.20.3 ([e9994b8](https://github.com/k8sgpt-ai/k8sgpt/commit/e9994b8d167d4f1d9c0d0dabf8385ff22cfd16a4))
* made json output prettier and improved output ([db40734](https://github.com/k8sgpt-ai/k8sgpt/commit/db40734a0db89850a2a685c9a7f5f5559875b7b3))
## [0.1.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.7...v0.1.8) (2023-04-03)
### Features
* add password flag for backend authentication ([#199](https://github.com/k8sgpt-ai/k8sgpt/issues/199)) ([075a940](https://github.com/k8sgpt-ai/k8sgpt/commit/075a940d2c9bdd8aa9162940ed46abad47d46998))
* adding shields to readme ([213ecd8](https://github.com/k8sgpt-ai/k8sgpt/commit/213ecd8e83933fabaa5d3d674c67958599dd72ce))
* adding unit testing and example ([35b838b](https://github.com/k8sgpt-ai/k8sgpt/commit/35b838bfafa248dbf3932c7a3ee708b1a1539f18))
* alias filter to filters ([dde4e83](https://github.com/k8sgpt-ai/k8sgpt/commit/dde4e833b0e87553dea4e5c1e17a14e303956bc1))
* analyzer ifacing ([426f562](https://github.com/k8sgpt-ai/k8sgpt/commit/426f562be83ed0e708a07b9e1900ac06fa017c27))
* service test ([44cc8f7](https://github.com/k8sgpt-ai/k8sgpt/commit/44cc8f7ad68d152ec577e57cab7d8d9ab9613378))
* test workflow ([5f30a4d](https://github.com/k8sgpt-ai/k8sgpt/commit/5f30a4ddf44ebff949bb0573f261667539a2dcfb))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.8 ([91fb065](https://github.com/k8sgpt-ai/k8sgpt/commit/91fb06530a21259da6e72c28342e743d2b481294))
### Other
* create linux packages ([#201](https://github.com/k8sgpt-ai/k8sgpt/issues/201)) ([67753be](https://github.com/k8sgpt-ai/k8sgpt/commit/67753be6f317c462ebe1d9a316f2b0c9684ca4e5))
* **deps:** pin dependencies ([#198](https://github.com/k8sgpt-ai/k8sgpt/issues/198)) ([f8291aa](https://github.com/k8sgpt-ai/k8sgpt/commit/f8291aab085209f9fee13a6c92c96076163e2e90))
## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)
### Features
* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))
* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))
* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))
### Bug Fixes
* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))
* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))
* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))
### Refactoring
* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)
### Other
* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))
* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))
* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))
* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))
* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))
* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))
### Docs
* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))
## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)
### Bug Fixes
* analysis detail not displayed when --explain ([869ba90](https://github.com/k8sgpt-ai/k8sgpt/commit/869ba909075a5543413fb6ae7fc79aa067c08da4))
## [0.1.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.4...v0.1.5) (2023-03-31)
### Features
* add & remove default filter(s) to analyze. ([32ddf66](https://github.com/k8sgpt-ai/k8sgpt/commit/32ddf6691ce083fd4283a1d5ac4b9f02e90df867))
* add filter command add "list" subcommand ([#159](https://github.com/k8sgpt-ai/k8sgpt/issues/159)) ([6e17c9e](https://github.com/k8sgpt-ai/k8sgpt/commit/6e17c9e285e3871bb8f694b734a8cd6fd02e60f0))
* check if filters does not empty on add & remove ([975813d](https://github.com/k8sgpt-ai/k8sgpt/commit/975813d3284719c877630ad20f90c6fe163283da))
* remove filter prefix on subcommand ([30faf84](https://github.com/k8sgpt-ai/k8sgpt/commit/30faf842541c0be6b6483f71f6cf04d5cafecef5))
* rework filters ([3ed545f](https://github.com/k8sgpt-ai/k8sgpt/commit/3ed545f33fb3ecb3827c03e8c89027c61386c44f))
* update filters add & remove to be more consistent ([9aa0e89](https://github.com/k8sgpt-ai/k8sgpt/commit/9aa0e8960ee340208b4749954c99867842ba58b9))
### Bug Fixes
* kubecontext flag has no effect ([a8bf451](https://github.com/k8sgpt-ai/k8sgpt/commit/a8bf45134ff3a72dc3e531d720f119790faff9d4))
* spelling on dupplicateFilters ([0a12448](https://github.com/k8sgpt-ai/k8sgpt/commit/0a124484a23789376258413e73628c7b1d7abded))
### Other
* renamed filter list file ([25f8dc3](https://github.com/k8sgpt-ai/k8sgpt/commit/25f8dc390cccd66965993f464351e671af11f8ac))
## [0.1.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.3...v0.1.4) (2023-03-30)
### Features
* add Ingress class validation ([#154](https://github.com/k8sgpt-ai/k8sgpt/issues/154)) ([b061566](https://github.com/k8sgpt-ai/k8sgpt/commit/b061566404ef80288ca29add2d401574109d44c0))
* output selected backend ([#153](https://github.com/k8sgpt-ai/k8sgpt/issues/153)) ([be061da](https://github.com/k8sgpt-ai/k8sgpt/commit/be061da5b65045938acd70ad2eb2d21b87d2d6bf))
### Bug Fixes
* now supports different kubeconfig and kubectx ([c8f3c94](https://github.com/k8sgpt-ai/k8sgpt/commit/c8f3c946b00c00cd185961a4fa777806da94014e))
### Refactoring
* removed sample flag ([0afd528](https://github.com/k8sgpt-ai/k8sgpt/commit/0afd52844b96579391f77698bf0555145b6d2be8))
## [0.1.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.2...v0.1.3) (2023-03-30)
### Features
* add secret validation to ingress analyzer ([#141](https://github.com/k8sgpt-ai/k8sgpt/issues/141)) ([86c7e81](https://github.com/k8sgpt-ai/k8sgpt/commit/86c7e81e18db02ebcbfe35d470682c982871375f))
* bugfix for output ([2eab0c5](https://github.com/k8sgpt-ai/k8sgpt/commit/2eab0c544fbb6026f6aea79b08d8f29c061acf2e))
* CODE_OF_CONDUCT.md ([#129](https://github.com/k8sgpt-ai/k8sgpt/issues/129)) ([fe73633](https://github.com/k8sgpt-ai/k8sgpt/commit/fe73633273c5c1f4188bca48471283535967d5aa))
* create-security.md ([27b8916](https://github.com/k8sgpt-ai/k8sgpt/commit/27b8916f297570907437686c6d958636fb249d50))
* improvement to analysis speed ([548039e](https://github.com/k8sgpt-ai/k8sgpt/commit/548039ebe62bb609c1aa288e5e49845850fd2dd8))
* init ingress analyzer ([#138](https://github.com/k8sgpt-ai/k8sgpt/issues/138)) ([fe683b7](https://github.com/k8sgpt-ai/k8sgpt/commit/fe683b71b84fe82459b0ffe366b4dcfa1c978cfe))
### Bug Fixes
* add Ingress in GetParent switch case ([14ba8d5](https://github.com/k8sgpt-ai/k8sgpt/commit/14ba8d555005f31fc2201cb8b61653093c19b8a7))
* bugfix for output ([#148](https://github.com/k8sgpt-ai/k8sgpt/issues/148)) ([172c2df](https://github.com/k8sgpt-ai/k8sgpt/commit/172c2df6c55f5fddbfec7f8526be5f2323d1b900))
* Change ObjectMeta value in Ingress analyser. ([bf49a51](https://github.com/k8sgpt-ai/k8sgpt/commit/bf49a51c62af450cff51a590547ef30989bd2e93))
* typo in description of the filter flag in analyze command ([#147](https://github.com/k8sgpt-ai/k8sgpt/issues/147)) ([f4765be](https://github.com/k8sgpt-ai/k8sgpt/commit/f4765bed1b1ad121a81b35878fdb866354b5e34a))
### Other
* **deps:** update google-github-actions/release-please-action digest to ee9822e ([#132](https://github.com/k8sgpt-ai/k8sgpt/issues/132)) ([01b2826](https://github.com/k8sgpt-ai/k8sgpt/commit/01b282647512a4eaebd42ab5847b5534de148d14))
### Docs
* add new slack link ([#134](https://github.com/k8sgpt-ai/k8sgpt/issues/134)) ([#135](https://github.com/k8sgpt-ai/k8sgpt/issues/135)) ([cad2b38](https://github.com/k8sgpt-ai/k8sgpt/commit/cad2b38d037658495024ec0166ebd3e936f65c2e))
## [0.1.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.1...v0.1.2) (2023-03-28)
### Features
* added namespace filter ([#127](https://github.com/k8sgpt-ai/k8sgpt/issues/127)) ([b78ab3d](https://github.com/k8sgpt-ai/k8sgpt/commit/b78ab3d9b503a256bf6ccf18276e20140ae17d1c))
* prefix templates ([#125](https://github.com/k8sgpt-ai/k8sgpt/issues/125)) ([65a568e](https://github.com/k8sgpt-ai/k8sgpt/commit/65a568e937a8fdacc179f5e8b1a021a0178c04f0))
### Bug Fixes
* readme code blocks ([#126](https://github.com/k8sgpt-ai/k8sgpt/issues/126)) ([c8b92aa](https://github.com/k8sgpt-ai/k8sgpt/commit/c8b92aaa0e2795aa8d65f84277c8adfe0f1d14e3))
* update README.md ([#119](https://github.com/k8sgpt-ai/k8sgpt/issues/119)) ([05abe97](https://github.com/k8sgpt-ai/k8sgpt/commit/05abe975dd859cd85096a1a7182f17b0437ad20f))
### Other
* added default issue template ([#96](https://github.com/k8sgpt-ai/k8sgpt/issues/96)) ([#121](https://github.com/k8sgpt-ai/k8sgpt/issues/121)) ([11c227b](https://github.com/k8sgpt-ai/k8sgpt/commit/11c227b82e16dac8b46cbd03bb04d9cc1c2b5ac3))
### Docs
* add new issue templates ([dbd305f](https://github.com/k8sgpt-ai/k8sgpt/commit/dbd305f901cca09b7148254c3aa7a7435504d6cc))
* add WSL gcc instructions ([4d5566b](https://github.com/k8sgpt-ai/k8sgpt/commit/4d5566b4df7aedf43edbeeb03130f0ba77dbed1a))
* added Windows and Linux instalation steps in README ([#116](https://github.com/k8sgpt-ai/k8sgpt/issues/116)) ([3bfb278](https://github.com/k8sgpt-ai/k8sgpt/commit/3bfb278f81a9c550ee37a88c0cb0377331802542))
* fix indentations ([a46416d](https://github.com/k8sgpt-ai/k8sgpt/commit/a46416dce0f5cee2d42b27525023b04af1a8e3c0))
* rename ISSUE_TEMPLATE ([#124](https://github.com/k8sgpt-ai/k8sgpt/issues/124)) ([cb4932c](https://github.com/k8sgpt-ai/k8sgpt/commit/cb4932c39df4903a4b48ae5f0428860027f76fd2))
## [0.1.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.0...v0.1.1) (2023-03-28)
### Features
* this stops service exiting the program ([6f90386](https://github.com/k8sgpt-ai/k8sgpt/commit/6f90386fc93b2e39e59832468922e8ba7210b8e5))
* updated readme ([e0141d1](https://github.com/k8sgpt-ai/k8sgpt/commit/e0141d1cf54b5b37b25a5caeb9d5c940b9410ea7))
### Bug Fixes
* short term solution for exhaustion ([5890e3a](https://github.com/k8sgpt-ai/k8sgpt/commit/5890e3a79c80a2973af2feb7d50e7f9c57c563c2))
### Other
* update README.md ([93b947f](https://github.com/k8sgpt-ai/k8sgpt/commit/93b947f261e401c10dde6dc1854e6e22187437d6))
* update root.go path ([2cb1c9c](https://github.com/k8sgpt-ai/k8sgpt/commit/2cb1c9c150d052bb3942d9f62ded9d54b0e1873e))
## [0.1.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.9...v0.1.0) (2023-03-28)

View File

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

View File

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

226
README.md
View File

@@ -2,124 +2,21 @@
<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/>
![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)
`k8sgpt` is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english.
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
It has SRE experience codified into it's analyzers and helps to pull out the most relevent information to enrich it with AI.
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
# Installation
## Linux/Mac via brew
## Quick Start
```
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.2/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.2/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.2/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.2/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.2/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.2/k8sgpt_amd64.apk
apk add k8sgpt_amd64.apk
```
<!---x-release-please-end-->x
</details>
<details>
<summary>Failing Installation on WSL or Linux (missing gcc)</summary>
When installing Homebrew on WSL or Linux, you may encounter the following error:
```
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be
built from the source. k8sgpt Install Clang or run brew install gcc.
```
If you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.
```
sudo apt-get update
sudo apt-get install build-essential
```
</details>
## Windows
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
tab based on your system architecture.
* Extract the downloaded package to your desired location. Configure the system *path* variable with the binary location
## Verify installation
* Run `k8sgpt version`
<hr>
## Quick Start
* Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)
* You can do this by running `k8sgpt generate` to open a browser link to generate it
* 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.
* You can do this by running `k8sgpt generate` to open a browser link to generate it
* Run `k8sgpt auth` to set it in k8sgpt.
* Run `k8sgpt analyze` to run a scan.
* And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.
@@ -127,29 +24,15 @@ 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
you will be able to write your own analyzers.
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but you will be able to write your own analyzers.
### Built in analyzers
#### Enabled by default
- [x] podAnalyzer
- [x] pvcAnalyzer
- [x] rsAnalyzer
- [x] serviceAnalyzer
- [x] eventAnalyzer
- [x] ingressAnalyzer
- [x] statefulSetAnalyzer
- [x] deploymentAnalyzer
- [x] cronJobAnalyzer
- [x] nodeAnalyzer
#### Optional
- [x] hpaAnalyzer
- [x] pdbAnalyzer
- [x] networkPolicyAnalyzer
## Usage
@@ -161,7 +44,6 @@ 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
@@ -176,36 +58,6 @@ 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_
```
@@ -217,82 +69,34 @@ k8sgpt analyze --explain
_Filter on resource_
```
k8sgpt analyze --explain --filter=Service
```
_Filter by namespace_
```
k8sgpt analyze --explain --filter=Pod --namespace=default
k8sgpt analyze --explain --resource=Service
```
_Output to JSON_
```
k8sgpt analyze --explain --filter=Service --output=json
k8sgpt analyze --explain --resource=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
- [ ] Custom AI/ML model backend support
- [ ] Custom analyzers
## What about kubectl-ai?
### What about kubectl-ai?
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.
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
the root cause of an issue.
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
### Configuration
`k8sgpt` stores config data in `~/.k8sgpt.yaml` the data is stored in plain text, including your OpenAI key.
## Contributing
### Contributing
Please read our [contributing guide](./CONTRIBUTING.md).
## Community
Find us on [Slack](https://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>
### Community
* Find us on [Slack](https://cloud-native.slack.com/channels/k8sgpt-ai)

View File

@@ -1,11 +0,0 @@
# Security Policy
## Supported Versions
We currently support the latest release for security patching and will deploy forward releases.
For example if there is a vulnerability in release `0.1.0` we will fix that release in version `0.1.1-fix` or `0.1.1`
## Reporting a Vulnerability
If you are aware of a vulnverability please feel free to disclose it responsibly to contact@k8sgpt.ai or to one of our maintainers in our Slack community.

View File

@@ -1,23 +1,28 @@
package analyze
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"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/spf13/cobra"
"github.com/spf13/viper"
)
var (
explain bool
backend string
output string
filters []string
language string
nocache bool
namespace string
anonymize bool
explain bool
backend string
output string
filters []string
language string
nocache bool
)
// AnalyzeCmd represents the problems command
@@ -29,53 +34,112 @@ var AnalyzeCmd = &cobra.Command{
provide you with a list of issues that need to be resolved`,
Run: func(cmd *cobra.Command, args []string) {
// AnalysisResult configuration
config, err := analysis.NewAnalysis(backend, language, filters, namespace, nocache, explain)
if err != nil {
color.Red("Error: %v", err)
// 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)
}
err = config.RunAnalysis()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if explain {
err := config.GetAIResults(output, anonymize)
if err != nil {
var aiClient ai.IAI
switch backendType {
case "openai":
aiClient = &ai.OpenAIClient{}
if err := aiClient.Configure(token, language); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
default:
color.Red("Backend not supported")
os.Exit(1)
}
ctx := context.Background()
// Get kubernetes client from viper
client := viper.Get("kubernetesClient").(*kubernetes.Client)
var analysisResults *[]analyzer.Analysis = &[]analyzer.Analysis{}
if err := analyzer.RunAnalysis(ctx, client, aiClient, explain, analysisResults); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Removed filtered results from slice
if len(filters) > 0 {
var filteredResults []analyzer.Analysis
for _, analysis := range *analysisResults {
for _, filter := range filters {
if strings.Contains(analysis.Kind, filter) {
filteredResults = append(filteredResults, analysis)
}
}
}
analysisResults = &filteredResults
}
var bar *progressbar.ProgressBar
if len(*analysisResults) > 0 {
bar = progressbar.Default(int64(len(*analysisResults)))
} else {
color.Green("{ \"status\": \"OK\" }")
os.Exit(0)
}
// This variable is used to store the results that will be printed
// It's necessary because the heap memory is lost when the function returns
var printOutput []analyzer.Analysis
for _, analysis := range *analysisResults {
if explain {
parsedText, err := analyzer.ParseViaAI(ctx, aiClient, analysis.Error, nocache)
if err != nil {
color.Red("Error: %v", err)
continue
}
analysis.Details = parsedText
bar.Add(1)
}
printOutput = append(printOutput, analysis)
}
// print results
switch output {
case "json":
output, err := config.JsonOutput()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
for n, analysis := range printOutput {
switch output {
case "json":
analysis.Error = analysis.Error[0:]
j, err := json.Marshal(analysis)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
fmt.Println(string(j))
default:
fmt.Printf("%s %s(%s): %s \n%s\n", color.CyanString("%d", n),
color.YellowString(analysis.Name), color.CyanString(analysis.ParentObject),
color.RedString(analysis.Error[0]), color.GreenString(analysis.Details))
}
fmt.Println(string(output))
default:
config.PrintOutput()
}
},
}
func init() {
// namespace flag
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
// no cache flag
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "c", false, "Do not use cached data")
// anonymize flag
AnalyzeCmd.Flags().BoolVarP(&anonymize, "anonymize", "a", false, "Anonymize data before sending it to the AI backend. This flag masks sensitive data, such as Kubernetes object names and labels, by replacing it with a key. However, please note that this flag does not currently apply to events.")
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "n", false, "Do not use cached data")
// array of strings flag
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)")
// explain flag
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analzyers (e.g. Pod,PersistentVolumeClaim,Service,ReplicaSet)")
AnalyzeCmd.Flags().BoolVarP(&explain, "explain", "e", false, "Explain the problem to me")
// add flag for backend
AnalyzeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")

View File

@@ -7,16 +7,13 @@ 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
password string
model string
backend string
)
// authCmd represents the auth command
@@ -26,65 +23,30 @@ var AuthCmd = &cobra.Command{
Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
Run: func(cmd *cobra.Command, args []string) {
// get ai configuration
var configAI ai.AIConfiguration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// 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())
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)
}
password = strings.TrimSpace(string(bytePassword))
}
// override the default backend if a flag is provided
if backend != "" {
backendType = backend
}
// create new provider object
newProvider := ai.AIProvider{
Name: backend,
Model: model,
Password: password,
fmt.Printf("Enter %s Key: ", backendType)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backendType,
err.Error())
os.Exit(1)
}
password := strings.TrimSpace(string(bytePassword))
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)
viper.Set(fmt.Sprintf("%s_key", backendType), password)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
@@ -96,8 +58,4 @@ 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")
}

View File

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

View File

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

View File

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

View File

@@ -1,74 +0,0 @@
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,6 +7,7 @@ import (
"github.com/spf13/viper"
"os/exec"
"runtime"
"time"
)
var (
@@ -30,6 +31,10 @@ 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")
},
}
@@ -41,15 +46,9 @@ func init() {
func openbrowser(url string) {
var err error
isGui := true
switch runtime.GOOS {
case "linux":
_, err = exec.LookPath("xdg-open")
if err != nil {
isGui = false
} else {
err = exec.Command("xdg-open", url).Start()
}
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
@@ -57,21 +56,7 @@ 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

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

@@ -1,35 +0,0 @@
/*
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

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

View File

@@ -1,50 +0,0 @@
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,26 +1,22 @@
package cmd
import (
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"os"
"path/filepath"
"github.com/k8sgpt-ai/k8sgpt/cmd/serve"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/integration"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"k8s.io/client-go/util/homedir"
)
var (
cfgFile string
kubecontext string
kubeconfig string
version string
cfgFile string
masterURL string
kubeconfig string
version string
)
// rootCmd represents the base command when called without any subcommands
@@ -46,19 +42,27 @@ func Execute(v string) {
func init() {
cobra.OnInitialize(initConfig)
var kubeconfigPath string
if home := homedir.HomeDir(); home != "" {
kubeconfigPath = filepath.Join(home, ".kube", "config")
}
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
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(&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.")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.git.yaml)")
rootCmd.PersistentFlags().StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
//Initialise the kubeconfig
kubernetesClient, err := kubernetes.NewClient(masterURL, kubeconfig)
if err != nil {
color.Red("Error initialising kubernetes client: %v", err)
}
viper.Set("kubernetesClient", kubernetesClient)
}
// initConfig reads in config file and ENV variables if set.
@@ -79,10 +83,6 @@ func initConfig() {
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.

View File

@@ -1,91 +0,0 @@
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\n", version)
cmd.Printf("k8sgpt version %s", version)
},
}

View File

@@ -1,4 +1,4 @@
FROM golang:1.20.3-alpine3.16 AS builder
FROM golang:1.20.2-alpine3.16 AS builder
ENV CGO_ENABLED=0

View File

@@ -1,42 +0,0 @@
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:dev-202304151719 #x-release-please-version
ports:
- containerPort: 8080
args: ["serve"]
resources:
limits:
cpu: "0.5"
memory: "256Mi"
requests:
cpu: "0.125"
memory: "64Mi"
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

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

View File

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

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

View File

@@ -1,13 +0,0 @@
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
View File

@@ -1,11 +0,0 @@
. 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"

166
go.mod
View File

@@ -3,179 +3,67 @@ 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/mittwald/go-helm-client v0.12.1
github.com/sashabaranov/go-openai v1.8.0
github.com/sashabaranov/go-openai v1.5.7
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.7.0
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
golang.org/x/term v0.7.0
helm.sh/helm/v3 v3.11.3
golang.org/x/term v0.6.0
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/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/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/emicklei/go-restful/v3 v3.9.0 // 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-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/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/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/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // 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/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/google/gofuzz v1.1.0 // 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/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-isatty v0.0.17 // 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/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rivo/uniseg v0.4.4 // 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/afero v1.9.3 // 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.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
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
google.golang.org/appengine v1.6.7 // 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
google.golang.org/protobuf v1.28.1 // 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
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
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

770
go.sum

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

48
pkg/ai/ai.go Normal file
View File

@@ -0,0 +1,48 @@
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,33 +1,8 @@
package ai
import (
"context"
)
import "context"
type IAI interface {
Configure(token string, model string, language string) error
Configure(token 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"`
}

View File

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

View File

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

View File

@@ -1,243 +0,0 @@
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"
)
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) 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) PrintOutput() {
fmt.Println("")
if len(a.Results) == 0 {
fmt.Println(color.GreenString("No problems detected"))
}
for n, result := range a.Results {
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.Text))
}
fmt.Println(color.GreenString(result.Details + "\n"))
}
}
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

@@ -1,154 +0,0 @@
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.JsonOutput()
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.JsonOutput()
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.JsonOutput()
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)
}

22
pkg/analyzer/analysis.go Normal file
View File

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

View File

@@ -1,101 +1,75 @@
package analyzer
import (
"fmt"
"os"
"context"
"encoding/base64"
"strings"
"github.com/fatih/color"
"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"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/spf13/viper"
)
var (
AnalyzerErrorsMetric = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "analyzer_errors",
Help: "Number of errors detected by analyzer",
}, []string{"analyzer_name", "object_name", "namespace"})
)
func RunAnalysis(ctx context.Context, client *kubernetes.Client,
aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
var coreAnalyzerMap = map[string]common.IAnalyzer{
"Pod": PodAnalyzer{},
"Deployment": DeploymentAnalyzer{},
"ReplicaSet": ReplicaSetAnalyzer{},
"PersistentVolumeClaim": PvcAnalyzer{},
"Service": ServiceAnalyzer{},
"Ingress": IngressAnalyzer{},
"StatefulSet": StatefulSetAnalyzer{},
"CronJob": CronJobAnalyzer{},
"Node": NodeAnalyzer{},
}
var additionalAnalyzerMap = map[string]common.IAnalyzer{
"HorizontalPodAutoScaler": HpaAnalyzer{},
"PodDisruptionBudget": PdbAnalyzer{},
"NetworkPolicy": NetworkPolicyAnalyzer{},
}
func ListFilters() ([]string, []string, []string) {
coreKeys := make([]string, 0, len(coreAnalyzerMap))
for k := range coreAnalyzerMap {
coreKeys = append(coreKeys, k)
err := AnalyzePod(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
additionalKeys := make([]string, 0, len(additionalAnalyzerMap))
for k := range additionalAnalyzerMap {
additionalKeys = append(additionalKeys, k)
err = AnalyzeReplicaSet(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
integrationProvider := integration.NewIntegration()
var integrationAnalyzers []string
err = AnalyzePersistentVolumeClaim(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
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())
err = AnalyzeEndpoints(ctx, client, aiClient, explain, analysisResults)
if err != nil {
return err
}
return nil
}
func ParseViaAI(ctx context.Context, aiClient ai.IAI, prompt []string,
nocache bool) (string, error) {
// parse the text with the AI backend
inputKey := strings.Join(prompt, " ")
// Check for cached data
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
// find in viper cache
if viper.IsSet(sEnc) && !nocache {
// retrieve data from cache
response := viper.GetString(sEnc)
if response == "" {
color.Red("error retrieving cached data")
return "", nil
}
}
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)
output, err := base64.StdEncoding.DecodeString(response)
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)
color.Red("error decoding cached data: %v", err)
return "", nil
}
return string(output), nil
}
return mergedMap
response, err := aiClient.GetCompletion(ctx, inputKey)
if err != nil {
color.Red("error getting completion: %v", err)
return "", nil
}
if !viper.IsSet(sEnc) {
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
if err := viper.WriteConfig(); err != nil {
color.Red("error writing config: %v", err)
return "", nil
}
}
return response, nil
}

View File

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

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

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

@@ -1,62 +0,0 @@
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,12 +8,36 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) {
// get the list of events
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx,
metav1.ListOptions{
FieldSelector: "involvedObject.name=" + name,
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,
})
if err != nil {

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,89 +0,0 @@
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,33 +1,26 @@
package analyzer
import (
"context"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"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"
)
type PodAnalyzer struct {
}
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
kind := "Pod"
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
"analyzer_name": kind,
})
func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient ai.IAI, explain bool, analysisResults *[]Analysis) error {
// search all namespaces for pods that are not running
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
list, err := client.GetClient().CoreV1().Pods("").List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
return err
}
var preAnalysis = map[string]common.PreAnalysis{}
var preAnalysis = map[string]PreAnalysis{}
for _, pod := range list.Items {
var failures []common.Failure
var failures []string
// Check for pending pods
if pod.Status.Phase == "Pending" {
@@ -35,10 +28,7 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
for _, containerStatus := range pod.Status.Conditions {
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
if containerStatus.Message != "" {
failures = append(failures, common.Failure{
Text: containerStatus.Message,
Sensitive: []common.Sensitive{},
})
failures = []string{containerStatus.Message}
}
}
}
@@ -49,49 +39,42 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.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, common.Failure{
Text: containerStatus.State.Waiting.Message,
Sensitive: []common.Sensitive{},
})
failures = append(failures, containerStatus.State.Waiting.Message)
}
}
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
// parse the event log and append details
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
evt, err := FetchLatestPodEvent(ctx, client, &pod)
if err != nil || evt == nil {
continue
}
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
failures = append(failures, common.Failure{
Text: evt.Message,
Sensitive: []common.Sensitive{},
})
failures = append(failures, evt.Message)
}
}
}
}
if len(failures) > 0 {
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = PreAnalysis{
Pod: pod,
FailureDetails: failures,
}
AnalyzerErrorsMetric.WithLabelValues(kind, pod.Name, pod.Namespace).Set(float64(len(failures)))
}
}
for key, value := range preAnalysis {
var currentAnalysis = common.Result{
Kind: kind,
var currentAnalysis = Analysis{
Kind: "Pod",
Name: key,
Error: value.FailureDetails,
}
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
parent, _ := util.GetParent(client, value.Pod.ObjectMeta)
currentAnalysis.ParentObject = parent
a.Results = append(a.Results, currentAnalysis)
*analysisResults = append(*analysisResults, currentAnalysis)
}
return a.Results, nil
return nil
}

View File

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

View File

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

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

View File

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

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

View File

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

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

View File

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

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

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

View File

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

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

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

@@ -1,103 +0,0 @@
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,43 +1,26 @@
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.Interface
RestClient rest.Interface
Config *rest.Config
client *kubernetes.Clientset
}
func (c *Client) GetConfig() *rest.Config {
return c.Config
func (c *Client) GetClient() *kubernetes.Clientset {
return c.client
}
func (c *Client) GetClient() kubernetes.Interface {
return c.Client
}
func NewClient(masterURL string, kubeconfig string) (*Client, error) {
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 {
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
&clientcmd.ConfigOverrides{
CurrentContext: kubecontext,
})
// create the clientset
config, err = clientConfig.ClientConfig()
kubeconfig :=
clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
config, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
if err != nil {
return nil, err
}
@@ -46,18 +29,7 @@ func NewClient(kubecontext 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,
RestClient: restClient,
Config: config,
client: clientSet,
}, nil
}

View File

@@ -1,107 +0,0 @@
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")
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())
}
}
output, err := config.JsonOutput()
if err != nil {
color.Red("Error: %v", err)
health.Failure++
fmt.Fprintf(w, err.Error())
}
health.Success++
fmt.Fprintf(w, string(output))
}
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,28 +2,11 @@ package util
import (
"context"
"encoding/base64"
"fmt"
"math/rand"
"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 {
@@ -67,86 +50,8 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
return GetParent(client, ds.ObjectMeta)
}
return "DaemonSet/" + ds.Name, false
case "Ingress":
ds, err := client.GetClient().NetworkingV1().Ingresses(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
if err != nil {
return "", false
}
if ds.OwnerReferences != nil {
return GetParent(client, ds.ObjectMeta)
}
return "Ingress/" + ds.Name, false
}
}
}
return meta.Name, false
}
func RemoveDuplicates(slice []string) ([]string, []string) {
set := make(map[string]bool)
duplicates := []string{}
for _, val := range slice {
if _, ok := set[val]; !ok {
set[val] = true
} else {
duplicates = append(duplicates, val)
}
}
uniqueSlice := make([]string, 0, len(set))
for val := range set {
uniqueSlice = append(uniqueSlice, val)
}
return uniqueSlice, duplicates
}
func SliceDiff(source, dest []string) []string {
mb := make(map[string]struct{}, len(dest))
for _, x := range dest {
mb[x] = struct{}{}
}
var diff []string
for _, x := range source {
if _, found := mb[x]; !found {
diff = append(diff, x)
}
}
return diff
}
func MaskString(input string) string {
key := make([]byte, len(input))
result := make([]rune, len(input))
rand.Read(key)
for i := range result {
result[i] = anonymizePattern[int(key[i])%len(anonymizePattern)]
}
return base64.StdEncoding.EncodeToString([]byte(string(result)))
}
func ReplaceIfMatch(text string, pattern string, replacement string) string {
re := regexp.MustCompile(fmt.Sprintf(`%s(\b)`, pattern))
if re.MatchString(text) {
text = re.ReplaceAllString(text, replacement)
}
return text
}
func GetCacheKey(provider string, 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
}

View File

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

View File

@@ -2,14 +2,7 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"helpers:pinGitHubActionDigests",
":gitSignOff"
],
"addLabels": ["dependencies"],
"postUpdateOptions": [
"gomodTidy",
"gomodMassage"
"helpers:pinGitHubActionDigests"
],
"packageRules": [
{
@@ -17,31 +10,9 @@
"matchCurrentVersion": "!/^0/",
"automerge": true
},
{
"matchManagers": ["gomod"],
"addLabels": ["go"]
},
{
"matchManagers": ["github-actions"],
"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"
]
"automerge": true
}
]
}