Compare commits

..

2 Commits

Author SHA1 Message Date
Thomas Schuetz
8e1920e0aa fix: ignore old client go packages in renovate
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-28 17:44:19 +02:00
Thomas Schuetz
8e8f77d01d docs: remove issue templates to use org wide ones
Signed-off-by: Thomas Schuetz <thomas.schuetz@t-sc.eu>
2023-04-27 16:25:10 +02:00
69 changed files with 687 additions and 2561 deletions

View File

@@ -12,7 +12,7 @@ jobs:
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: golangci-lint
uses: reviewdog/action-golangci-lint@79d32f10b2ea0d4cebb755d849b048c4b40c3d50 # v2
uses: reviewdog/action-golangci-lint@53f8eabb87b40b1a2c63ec75b0d418bd0f4aa919 # v2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-check

View File

@@ -45,11 +45,11 @@ jobs:
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: '1.20'
- name: Download Syft
uses: anchore/sbom-action/download-syft@4d571ad1038a9cc29d676154ef265ab8f9027042 # v0.14.2
uses: anchore/sbom-action/download-syft@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4
with:
@@ -104,7 +104,7 @@ jobs:
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
- name: Generate SBOM
uses: anchore/sbom-action@4d571ad1038a9cc29d676154ef265ab8f9027042 # v0.14.2
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
with:
image: ${{ env.IMAGE_TAG }}
artifact-name: sbom-${{ env.IMAGE_NAME }}

View File

@@ -19,13 +19,9 @@ jobs:
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
- name: Set up Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Unit Test
run: make test
# - name: Fmt Test
# run: fmtFiles=$(make fmt); if [ "$fmtFiles" != "" ];then exit 1; fi
- name: Test
run: go test -v ./...

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.DS_Store
k8sgpt*
!charts/k8sgpt
*.vscode
dist/

View File

@@ -15,8 +15,6 @@ builds:
- darwin
ldflags:
- -s -w -X main.version={{.Version}}
- -s -w -X main.commit={{.ShortCommit}}
- -s -w -X main.Date={{.CommitDate}}
nfpms:
- file_name_template: '{{ .ProjectName }}_{{ .Arch }}'

View File

@@ -1 +1 @@
{".":"0.3.4"}
{".":"0.2.8"}

View File

@@ -1,144 +1,5 @@
# Changelog
## [0.3.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.3...v0.3.4) (2023-05-22)
### Bug Fixes
* **deps:** update module github.com/aws/aws-sdk-go to v1.44.266 ([#446](https://github.com/k8sgpt-ai/k8sgpt/issues/446)) ([edda743](https://github.com/k8sgpt-ai/k8sgpt/commit/edda743fa2bf4b5ae2551c981447a5912a459bb4))
* **deps:** update module github.com/stretchr/testify to v1.8.3 ([#442](https://github.com/k8sgpt-ai/k8sgpt/issues/442)) ([fe450eb](https://github.com/k8sgpt-ai/k8sgpt/commit/fe450eb69da0645328e60e2d7b0852ffdb996dee))
### Other
* add more filter releavent UT in analysis_test.go ([#435](https://github.com/k8sgpt-ai/k8sgpt/issues/435)) ([36995fd](https://github.com/k8sgpt-ai/k8sgpt/commit/36995fd4ed41c8c83ebc308f8ec2a4bfe530f7dc))
## [0.3.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.2...v0.3.3) (2023-05-20)
### Features
* caching ([#439](https://github.com/k8sgpt-ai/k8sgpt/issues/439)) ([948dae5](https://github.com/k8sgpt-ai/k8sgpt/commit/948dae5e288ec3bb0165eb3ce32171b12003f9c7))
* rework auth commands ([#438](https://github.com/k8sgpt-ai/k8sgpt/issues/438)) ([c659a87](https://github.com/k8sgpt-ai/k8sgpt/commit/c659a875fc296849a3703bfd7f0c4796f44bdf5a))
### Bug Fixes
* append coreAnalyzer if active_filter is empty and integration is added ([#441](https://github.com/k8sgpt-ai/k8sgpt/issues/441)) ([b7dc384](https://github.com/k8sgpt-ai/k8sgpt/commit/b7dc3845476759bedb3a55f77c8779e4a9f460dd))
* **deps:** update kubernetes packages to v0.27.2 ([#436](https://github.com/k8sgpt-ai/k8sgpt/issues/436)) ([d13b913](https://github.com/k8sgpt-ai/k8sgpt/commit/d13b91301cab5e05349b68716cd506fa1705f36f))
* **deps:** update module github.com/aws/aws-sdk-go to v1.44.265 ([#445](https://github.com/k8sgpt-ai/k8sgpt/issues/445)) ([c588e96](https://github.com/k8sgpt-ai/k8sgpt/commit/c588e963de3f238f07200d6cf09fe6f9781484f5))
* docker version ([#444](https://github.com/k8sgpt-ai/k8sgpt/issues/444)) ([1f767eb](https://github.com/k8sgpt-ai/k8sgpt/commit/1f767ebd2e31e61decab36218b1b85f2b3b6207d))
* use coreAnalyzer if there are no filters selected and no active_filters ([#432](https://github.com/k8sgpt-ai/k8sgpt/issues/432)) ([f0d3f36](https://github.com/k8sgpt-ai/k8sgpt/commit/f0d3f36f6d56bd76248590c0b841dffb7769a2ee))
## [0.3.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.1...v0.3.2) (2023-05-16)
### Features
* added the ability to set a user default AI provider ([#427](https://github.com/k8sgpt-ai/k8sgpt/issues/427)) ([cbe2fb4](https://github.com/k8sgpt-ai/k8sgpt/commit/cbe2fb4a4c160a0a24b3fb4602cae8e5eebd6aa0))
### Bug Fixes
* improve default_prompt ([#406](https://github.com/k8sgpt-ai/k8sgpt/issues/406)) ([06542b4](https://github.com/k8sgpt-ai/k8sgpt/commit/06542b4bf1aec193f11a40526a1b60be01972e66))
* missing validation for backend option in remove command ([#429](https://github.com/k8sgpt-ai/k8sgpt/issues/429)) ([af826d5](https://github.com/k8sgpt-ai/k8sgpt/commit/af826d500fef0469b958250161b0827aa4ab2933))
### Other
* **deps:** bump github.com/docker/distribution ([#428](https://github.com/k8sgpt-ai/k8sgpt/issues/428)) ([3099909](https://github.com/k8sgpt-ai/k8sgpt/commit/30999091136c64173e5c15b789036c85f8b855f3))
* **deps:** update actions/setup-go digest to fac708d ([#422](https://github.com/k8sgpt-ai/k8sgpt/issues/422)) ([097c791](https://github.com/k8sgpt-ai/k8sgpt/commit/097c7912b0572d0461f08af12e9f21c6618c13e4))
* **deps:** update reviewdog/action-golangci-lint digest to 79d32f1 ([#425](https://github.com/k8sgpt-ai/k8sgpt/issues/425)) ([032576c](https://github.com/k8sgpt-ai/k8sgpt/commit/032576c728751522fe6cd8f255366cc3d5c0f23c))
## [0.3.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.0...v0.3.1) (2023-05-15)
### Features
* add error message if analyze request fail ([#393](https://github.com/k8sgpt-ai/k8sgpt/issues/393)) ([639aa12](https://github.com/k8sgpt-ai/k8sgpt/commit/639aa12931b3fc9f99a4b34f33f583b9f427f40c))
* add remove command to remove a backend AI provider ([#395](https://github.com/k8sgpt-ai/k8sgpt/issues/395)) ([c5b72ee](https://github.com/k8sgpt-ai/k8sgpt/commit/c5b72eee165a29d4ed59b0ec2f19e9f58267840d))
* filters api ([#407](https://github.com/k8sgpt-ai/k8sgpt/issues/407)) ([e5e613a](https://github.com/k8sgpt-ai/k8sgpt/commit/e5e613acee0a99f108dd90affc55a18dee42ad9c))
* use correct port to metrics ([#390](https://github.com/k8sgpt-ai/k8sgpt/issues/390)) ([5d4e591](https://github.com/k8sgpt-ai/k8sgpt/commit/5d4e591f11e555cac851205fff158ef22f702c33))
### Bug Fixes
* clusterole name ([#392](https://github.com/k8sgpt-ai/k8sgpt/issues/392)) ([123b8a6](https://github.com/k8sgpt-ai/k8sgpt/commit/123b8a66eed1af41b7bd4e558ba4f0f8ef947e98))
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20230515081240-6b5b845c638e.1 ([#397](https://github.com/k8sgpt-ai/k8sgpt/issues/397)) ([a1f98ad](https://github.com/k8sgpt-ai/k8sgpt/commit/a1f98ad78ecd9a84d26e7a59c340e5410524e561))
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go to v1.28.1-20230510140658-54288a50e81c.4 ([#398](https://github.com/k8sgpt-ai/k8sgpt/issues/398)) ([50916f2](https://github.com/k8sgpt-ai/k8sgpt/commit/50916f2c93fb19b935f835eda1ba78b7c0465fe6))
* **deps:** update module google.golang.org/grpc to v1.55.0 ([#389](https://github.com/k8sgpt-ai/k8sgpt/issues/389)) ([8cfb717](https://github.com/k8sgpt-ai/k8sgpt/commit/8cfb717dc15a6af489a16282d38d3495518bc919))
* **deps:** update module helm.sh/helm/v3 to v3.12.0 ([#396](https://github.com/k8sgpt-ai/k8sgpt/issues/396)) ([c1410d1](https://github.com/k8sgpt-ai/k8sgpt/commit/c1410d169945341e9a635e12c2adcc87a08f8a09))
* update engine's cmd flag to match the new cli layout ([#400](https://github.com/k8sgpt-ai/k8sgpt/issues/400)) ([aafe669](https://github.com/k8sgpt-ai/k8sgpt/commit/aafe669739aa8c38611d13deb08706096c7893e0))
### Other
* gofmt fix and enable in CI ([#414](https://github.com/k8sgpt-ai/k8sgpt/issues/414)) ([e66de8c](https://github.com/k8sgpt-ai/k8sgpt/commit/e66de8c4cea1213cda1db609f07cbb0c8f6591c3))
* make go-lint happy ([#405](https://github.com/k8sgpt-ai/k8sgpt/issues/405)) ([ed73485](https://github.com/k8sgpt-ai/k8sgpt/commit/ed73485d92af0329915633d51c7eccdbce9b37cc))
## [0.3.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.9...v0.3.0) (2023-05-09)
### ⚠ BREAKING CHANGES
* migrate api to grpc ([#386](https://github.com/k8sgpt-ai/k8sgpt/issues/386))
### Features
* add auth commands ([#369](https://github.com/k8sgpt-ai/k8sgpt/issues/369)) ([00aaae8](https://github.com/k8sgpt-ai/k8sgpt/commit/00aaae86d88812bd6b6be290ba440ea583ccc01c))
* migrate api to grpc ([#386](https://github.com/k8sgpt-ai/k8sgpt/issues/386)) ([9998e76](https://github.com/k8sgpt-ai/k8sgpt/commit/9998e7620d2803b82b241482649449507040add3))
### Bug Fixes
* **deps:** update module github.com/sashabaranov/go-openai to v1.9.3 ([#378](https://github.com/k8sgpt-ai/k8sgpt/issues/378)) ([045a063](https://github.com/k8sgpt-ai/k8sgpt/commit/045a06350bf41d4177e67316978af8fcf02ff19a))
* **deps:** update module golang.org/x/term to v0.8.0 ([#382](https://github.com/k8sgpt-ai/k8sgpt/issues/382)) ([65fff11](https://github.com/k8sgpt-ai/k8sgpt/commit/65fff11e585f8074fb77124b25339a09da313970))
### Docs
* update README ([#383](https://github.com/k8sgpt-ai/k8sgpt/issues/383)) ([d6bcb96](https://github.com/k8sgpt-ai/k8sgpt/commit/d6bcb96105a549eb772b790704c7ec27e374eafd))
### Other
* **deps:** update anchore/sbom-action action to v0.14.2 ([#387](https://github.com/k8sgpt-ai/k8sgpt/issues/387)) ([9192b26](https://github.com/k8sgpt-ai/k8sgpt/commit/9192b26fab2ce09c8a480256a15723ae788612c3))
* fix the logo URL ([#384](https://github.com/k8sgpt-ai/k8sgpt/issues/384)) ([b6b0612](https://github.com/k8sgpt-ai/k8sgpt/commit/b6b06123db8914cae09dfa96edd92aff83823bdf))
## [0.2.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.8...v0.2.9) (2023-05-03)
### Features
* add additionalLabels to Service Monitor ([#366](https://github.com/k8sgpt-ai/k8sgpt/issues/366)) ([a89a5cf](https://github.com/k8sgpt-ai/k8sgpt/commit/a89a5cfa2efd365cfc1c501c7055a315949c5f97))
* add azure openai provider ([#309](https://github.com/k8sgpt-ai/k8sgpt/issues/309)) ([d8357ce](https://github.com/k8sgpt-ai/k8sgpt/commit/d8357ceb949e04d9dd21276a1d1dfcb60010c37a))
* add helm chart ([#318](https://github.com/k8sgpt-ai/k8sgpt/issues/318)) ([5af8178](https://github.com/k8sgpt-ai/k8sgpt/commit/5af817880278771cf0b25d0936b90a45c089218c))
* improving readme ([7471b76](https://github.com/k8sgpt-ai/k8sgpt/commit/7471b7679469bc7416ee35061a13e8442bfc532c))
* rework output format ([#368](https://github.com/k8sgpt-ai/k8sgpt/issues/368)) ([8b49f70](https://github.com/k8sgpt-ai/k8sgpt/commit/8b49f708f364569994801757cf72982291c0de82))
### Bug Fixes
* **deps:** update module github.com/aquasecurity/trivy-operator to v0.13.2 ([#353](https://github.com/k8sgpt-ai/k8sgpt/issues/353)) ([363294c](https://github.com/k8sgpt-ai/k8sgpt/commit/363294c310ca1a6666d4d2dcba7e232387f0d41f))
* **deps:** update module github.com/prometheus/client_golang to v1.15.1 ([#374](https://github.com/k8sgpt-ai/k8sgpt/issues/374)) ([799869b](https://github.com/k8sgpt-ai/k8sgpt/commit/799869bcef76be0d5b2169646de106bd3b441c50))
* **deps:** update module github.com/sashabaranov/go-openai to v1.9.1 ([#363](https://github.com/k8sgpt-ai/k8sgpt/issues/363)) ([766a15b](https://github.com/k8sgpt-ai/k8sgpt/commit/766a15b25a2669b101c717013dae0ea4f5d0daa3))
* **deps:** update module github.com/sashabaranov/go-openai to v1.9.2 ([#375](https://github.com/k8sgpt-ai/k8sgpt/issues/375)) ([8b82d59](https://github.com/k8sgpt-ai/k8sgpt/commit/8b82d59bd7a6837a543467f9d76c66e70f267b85))
* update CONTRIBUTING.md Slack URL ([#373](https://github.com/k8sgpt-ai/k8sgpt/issues/373)) ([e0200e7](https://github.com/k8sgpt-ai/k8sgpt/commit/e0200e7fa05597b52c9f25fc2e9b744aea764b9a))
### Docs
* remove issue templates to use org wide ones ([#352](https://github.com/k8sgpt-ai/k8sgpt/issues/352)) ([3051b1c](https://github.com/k8sgpt-ai/k8sgpt/commit/3051b1ca343d739e48ff05c96468040adefc929a))
* update README.md ([#356](https://github.com/k8sgpt-ai/k8sgpt/issues/356)) ([4e146fb](https://github.com/k8sgpt-ai/k8sgpt/commit/4e146fb152b79a43f3163a94389022b4325126d4))
### Other
* added changing banners ([#367](https://github.com/k8sgpt-ai/k8sgpt/issues/367)) ([4f6e833](https://github.com/k8sgpt-ai/k8sgpt/commit/4f6e833d3427adf82438186f52eee40293c50cd0))
* **deps:** update golang docker tag to v1.20.4 ([#370](https://github.com/k8sgpt-ai/k8sgpt/issues/370)) ([9faa694](https://github.com/k8sgpt-ai/k8sgpt/commit/9faa69422dab812ec062c10a8fffb73160a3cd21))
* **deps:** update reviewdog/action-golangci-lint digest to f5d8591 ([#362](https://github.com/k8sgpt-ai/k8sgpt/issues/362)) ([b8a5f3b](https://github.com/k8sgpt-ai/k8sgpt/commit/b8a5f3bab85e28f83259739958fe22ca0ada211a))
* updated logo ([#365](https://github.com/k8sgpt-ai/k8sgpt/issues/365)) ([6431be7](https://github.com/k8sgpt-ai/k8sgpt/commit/6431be7771bd7c34d6beac14470bc6918b1793c4))
## [0.2.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.7...v0.2.8) (2023-04-27)

View File

@@ -17,7 +17,7 @@ We're happy that you want to contribute to this project. Please read the section
- We are also happy to help you find something to work on. Just reach out to us.
**Getting in touch with the community**
* Join our [#k8sgpt slack channel](https://join.slack.com/t/k8sgpt/shared_invite/zt-1rwe5fpzq-VNtJK8DmYbbm~iWL1H34nw)
* Join our [#k8sgpt slack channel](https://slack.cloud-native.io/channels/k8sgpt)
* Introduce yourself on the slack channel or open an issue to let us know that you are interested in contributing
**Discuss issues**

View File

@@ -10,8 +10,6 @@ ROOT_PACKAGE=github.com/k8sgpt-ai/k8sgpt
SHELL := /bin/bash
DIRS=$(shell ls)
GO=go
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
.DEFAULT_GOAL := help
@@ -54,7 +52,7 @@ all: tidy add-copyright lint cover build
build:
@echo "$(shell go version)"
@echo "===========> Building binary $(BUILDAPP) *[Git Info]: $(VERSION)-$(GIT_COMMIT)"
@export CGO_ENABLED=0 && go build -o $(BUILDAPP) -ldflags "-s -w -X main.version=dev -X main.commit=$$(git rev-parse --short HEAD) -X main.date=$$(date +%FT%TZ)" $(BUILDFILE)
@export CGO_ENABLED=0 && go build -o $(BUILDAPP) -ldflags '-s -w' $(BUILDFILE)
## tidy: tidy go.mod
.PHONY: tidy
@@ -63,21 +61,17 @@ tidy:
## deploy: Deploy k8sgpt
.PHONY: deploy
deploy: helm
deploy:
@echo "===========> Deploying k8sgpt"
$(HELM) install k8sgpt charts/k8sgpt -n k8sgpt --create-namespace
## update: Update k8sgpt
.PHONY: update
update: helm
@echo "===========> Updating k8sgpt"
$(HELM) upgrade k8sgpt charts/k8sgpt -n k8sgpt
@$(call funcsecret)
## undeploy: Undeploy k8sgpt
.PHONY: undeploy
undeploy: helm
undeploy:
@echo "===========> Undeploying k8sgpt"
$(HELM) uninstall k8sgpt -n k8sgpt
kubectl delete secret ai-backend-secret --namespace=k8sgpt
kubectl delete -f container/manifests
kubectl delete ns k8sgpt
## docker-build: Build docker image
.PHONY: docker-build
@@ -85,6 +79,7 @@ docker-build:
@echo "===========> Building docker image"
docker buildx build --build-arg=VERSION="$$(git describe --tags --abbrev=0)" --build-arg=COMMIT="$$(git rev-parse --short HEAD)" --build-arg DATE="$$(date +%FT%TZ)" --platform="linux/amd64,linux/arm64" -t ${IMG} -f container/Dockerfile . --push
## fmt: Run go fmt against code.
.PHONY: fmt
fmt:
@@ -142,16 +137,11 @@ copyright.add: tools.verify.addlicense
# @addlicense -y $(shell date +"%Y") -v -c "K8sgpt AI." -f $(LICENSE_TEMPLATE) $(CODE_DIRS)
@echo "===========> End the copyright is added..."
# =====
# Tools
HELM_VERSION ?= v3.11.3
helm:
if ! test -f $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); then \
curl -L https://get.helm.sh/helm-$(HELM_VERSION)-$(GOOS)-$(GOARCH).tar.gz | tar xz; \
mv $(GOOS)-$(GOARCH)/helm $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); \
chmod +x $(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH); \
rm -rf ./$(GOOS)-$(GOARCH)/; \
fi
HELM=$(OUTPUT_DIR)/helm-$(GOOS)-$(GOARCH)
define funcsecret
ifndef SECRET
$(error SECRET environment variable is not set)
endif
kubectl create ns k8sgpt || true
kubectl create secret generic ai-backend-secret --from-literal=secret-key=$(SECRET) --namespace=k8sgpt || true
kubectl apply -f container/manifests
endef

283
README.md
View File

@@ -1,8 +1,4 @@
<picture>
<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/>
<img src="./images/banner-white.png" width="600px;" />
![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)
@@ -16,12 +12,10 @@ It has SRE experience codified into its analyzers and helps to pull out the most
<a href="https://www.producthunt.com/posts/k8sgpt?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-k8sgpt" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=389489&theme=light" alt="K8sGPT - K8sGPT&#0032;gives&#0032;Kubernetes&#0032;Superpowers&#0032;to&#0032;everyone | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<img src="images/demo4.gif" width=650px; />
# CLI Installation
# Installation
### Linux/Mac via brew
## Linux/Mac via brew
```
brew tap k8sgpt-ai/k8sgpt
@@ -34,7 +28,7 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.4/k8sgpt_386.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/k8sgpt_386.rpm
sudo rpm -ivh k8sgpt_386.rpm
```
<!---x-release-please-end-->
@@ -43,7 +37,7 @@ brew install k8sgpt
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.4/k8sgpt_amd64.rpm
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/k8sgpt_amd64.rpm
sudo rpm -ivh -i k8sgpt_amd64.rpm
```
<!---x-release-please-end-->
@@ -55,7 +49,7 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.4/k8sgpt_386.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/k8sgpt_386.deb
sudo dpkg -i k8sgpt_386.deb
```
<!---x-release-please-end-->
@@ -63,7 +57,7 @@ brew install k8sgpt
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.4/k8sgpt_amd64.deb
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/k8sgpt_amd64.deb
sudo dpkg -i k8sgpt_amd64.deb
```
<!---x-release-please-end-->
@@ -76,14 +70,14 @@ brew install k8sgpt
**32 bit:**
<!---x-release-please-start-version-->
```
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.4/k8sgpt_386.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/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.3.4/k8sgpt_amd64.apk
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.8/k8sgpt_amd64.apk
apk add k8sgpt_amd64.apk
```
<!---x-release-please-end-->x
@@ -106,29 +100,31 @@ If you install gcc as suggested, the problem will persist. Therefore, you need t
</details>
### Windows
## 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
## Operator Installation
To install within a Kubernetes cluster please use our `k8sgpt-operator` with installation instructions available [here](https://github.com/k8sgpt-ai/k8sgpt-operator)
## Verify installation
_This mode of operation is ideal for continuous monitoring of your cluster and can integrate with your existing monitoring such as Prometheus and Alertmanager._
* 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 add` to set it in k8sgpt.
* Run `k8sgpt auth` to set it in k8sgpt.
* You can provide the password directly using the `--password` flag.
* Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis.
* Run `k8sgpt analyze` to run a scan.
* And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.
<img src="images/demo4.gif" width=650px; />
## Analyzers
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
@@ -155,13 +151,69 @@ you will be able to write your own analyzers.
- [x] pdbAnalyzer
- [x] networkPolicyAnalyzer
## Examples
## Usage
```
Kubernetes debugging powered by AI
Usage:
k8sgpt [command]
Available Commands:
analyze This command will find problems within your Kubernetes cluster
auth Authenticate with your chosen backend
completion Generate the autocompletion script for the specified shell
filters Manage filters for analyzing Kubernetes resources
generate Generate Key for your chosen backend (opens browser)
help Help about any command
integration Intergrate another tool into K8sGPT
serve Runs k8sgpt as a server
version Print the version number of k8sgpt
Flags:
--config string config file (default is $HOME/.k8sgpt.yaml)
-h, --help help for k8sgpt
--kubeconfig string Path to a kubeconfig. Only required if out-of-cluster. (default "$HOME/.kube/config")
--kubecontext string Kubernetes context to use. Only required if out-of-cluster.
Use "k8sgpt [command] --help" for more information about a command.
```
_Manage filters_
_List filters_
```
k8sgpt filters list
```
_Add default filters_
```
k8sgpt filters add [filter(s)]
```
### Examples :
- Simple filter : `k8sgpt filters add Service`
- Multiple filters : `k8sgpt filters add Ingress,Pod`
_Add default filters_
```
k8sgpt filters remove [filter(s)]
```
### Examples :
- Simple filter : `k8sgpt filters remove Service`
- Multiple filters : `k8sgpt filters remove Ingress,Pod`
_Run a scan with the default analyzers_
```
k8sgpt generate
k8sgpt auth add
k8sgpt auth
k8sgpt analyze --explain
```
@@ -188,53 +240,41 @@ _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>
<summary> Using filters </summary>
_List filters_
```
k8sgpt filters list
1. Error reported during analysis:
```bash
Error: HorizontalPodAutoscaler uses StatefulSet/fake-deployment as ScaleTargetRef which does not exist.
```
_Add default filters_
```
k8sgpt filters add [filter(s)]
2. Payload sent to the AI backend:
```bash
Error: HorizontalPodAutoscaler uses StatefulSet/tGLcCRcHa1Ce5Rs as ScaleTargetRef which does not exist.
```
### Examples :
- Simple filter : `k8sgpt filters add Service`
- Multiple filters : `k8sgpt filters add Ingress,Pod`
_Remove default filters_
```
k8sgpt filters remove [filter(s)]
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.
```
### Examples :
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.
```
- Simple filter : `k8sgpt filters remove Service`
- Multiple filters : `k8sgpt filters remove Ingress,Pod`
**Anonymization does not currently apply to events.**
</details>
### Additional commands
<details>
<summary> Additional commands </summary>
_List configured backends_
```
k8sgpt auth list
```
_Remove configured backends_
```
k8sgpt auth remove $MY_BACKEND1,$MY_BACKEND2..
```
_Manage integrations_
_List integrations_
@@ -271,16 +311,14 @@ _Analysis with serve mode_
```
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
```
</details>
## Key Features
<details>
<summary> LocalAI provider </summary>
## Running local models
To run local models, it is possible to use OpenAI compatible APIs, for instance [LocalAI](https://github.com/go-skynet/LocalAI) which uses [llama.cpp](https://github.com/ggerganov/llama.cpp) and [ggml](https://github.com/ggerganov/ggml) to run inference on consumer-grade hardware. Models supported by LocalAI for instance are Vicuna, Alpaca, LLaMA, Cerebras, GPT4ALL, GPT4ALL-J and koala.
<details>
To run local inference, you need to download the models first, for instance you can find `ggml` compatible models in [huggingface.com](https://huggingface.co/models?search=ggml) (for example vicuna, alpaca and koala).
@@ -290,12 +328,14 @@ To start the API server, follow the instruction in [LocalAI](https://github.com/
### Run k8sgpt
To run k8sgpt, run `k8sgpt auth new` with the `localai` backend:
To run k8sgpt, run `k8sgpt auth` with the `localai` backend:
```
k8sgpt auth new --backend localai --model <model_name> --baseurl http://localhost:8080/v1
k8sgpt auth --backend localai --model <model_name> --baseurl http://localhost:8080/v1
```
When being asked for an API key, just press enter.
Now you can analyze with the `localai` backend:
```
@@ -304,93 +344,8 @@ k8sgpt analyze --explain --backend localai
</details>
<details>
<summary> AzureOpenAI provider </summary>
## Configuration
<em>Prerequisites:</em> an Azure OpenAI deployment is needed, please visit MS official [documentation](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource) to create your own.
To authenticate with k8sgpt, you will need the Azure OpenAI endpoint of your tenant `"https://your Azure OpenAI Endpoint"`, the api key to access your deployment, the deployment name of your model and the model name itself.
To run k8sgpt, run `k8sgpt auth` with the `azureopenai` backend:
```
k8sgpt auth add --backend azureopenai --baseurl https://<your Azure OpenAI endpoint> --engine <deployment_name> --model <model_name>
```
Lastly, enter your Azure API key, after the prompt.
Now you are ready to analyze with the azure openai backend:
```
k8sgpt analyze --explain --backend azureopenai
```
</details>
<details>
<summary>Setting a new default AI provider</summary>
There may be scenarios where you wish to have K8sGPT plugged into several default AI providers. In this case you may wish to use one as a new default, other than OpenAI which is the project default.
_To view available providers_
```
k8sgpt auth list
Default:
> openai
Active:
> openai
> azureopenai
Unused:
> localai
> noopai
```
_To set a new default provider_
```
k8sgpt auth default -p azureopenai
Default provider set to azureopenai
```
</details>
<details>
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.
<summary> Anonymization </summary>
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>
<details>
<summary> Configuration management</summary>
`k8sgpt` stores config data in the `$XDG_CONFIG_HOME/k8sgpt/k8sgpt.yaml` file. The data is stored in plain text, including your OpenAI key.
Config file locations:
@@ -399,38 +354,6 @@ Config file locations:
| MacOS | ~/Library/Application Support/k8sgpt/k8sgpt.yaml |
| Linux | ~/.config/k8sgpt/k8sgpt.yaml |
| Windows | %LOCALAPPDATA%/k8sgpt/k8sgpt.yaml |
</details>
<details>
There may be scenarios where caching remotely is prefered.
In these scenarios K8sGPT supports AWS S3 Integration.
<summary> Remote caching </summary>
_As a prerequisite `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are required as environmental variables._
_Adding a remote cache_
Note: this will create the bucket if it does not exist
```
k8sgpt cache add --region <aws region> --bucket <name>
```
_Listing cache items_
```
k8sgpt cache list
```
_Removing the remote cache_
Note: this will not delete the bucket
```
k8sgpt cache remove --bucket <name>
```
</details>
## Documentation
Find our official documentation available [here](https://docs.k8sgpt.ai)
## Contributing

View File

@@ -1,6 +0,0 @@
apiVersion: v2
appVersion: v0.3.0 #x-release-please-version
description: A Helm chart for K8SGPT
name: k8sgpt
type: application
version: 1.0.0

View File

@@ -1,44 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "k8sgpt.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "k8sgpt.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "k8sgpt.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "k8sgpt.labels" -}}
helm.sh/chart: {{ include "k8sgpt.chart" . }}
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
{{- end }}
{{- end }}

View File

@@ -1,47 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
{{- if .Values.deployment.annotations }}
annotations:
{{- toYaml .Values.deployment.annotations | nindent 4 }}
{{- end }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
serviceAccountName: k8sgpt
containers:
- name: k8sgpt-container
imagePullPolicy: {{ .Values.deployment.imagePullPolicy }}
image: {{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion }}
ports:
- containerPort: 8080
args: ["serve"]
{{- if .Values.deployment.resources }}
resources:
{{- toYaml .Values.deployment.resources | nindent 10 }}
{{- end }}
env:
- name: K8SGPT_MODEL
value: {{ .Values.deployment.env.model }}
- name: K8SGPT_BACKEND
value: {{ .Values.deployment.env.backend }}
{{- if .Values.secret.secretKey }}
- name: K8SGPT_PASSWORD
valueFrom:
secretKeyRef:
name: ai-backend-secret
key: secret-key
{{- end }}

View File

@@ -1,15 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}
subjects:
- kind: ServiceAccount
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
roleRef:
kind: ClusterRole
name: {{ template "k8sgpt.fullname" . }}
apiGroup: rbac.authorization.k8s.io

View File

@@ -1,7 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}

View File

@@ -1,10 +0,0 @@
{{- if .Values.secret.secretKey }}
apiVersion: v1
data:
secret-key: {{ .Values.secret.secretKey }}
kind: Secret
metadata:
name: ai-backend-secret
namespace: {{ .Release.Namespace | quote }}
type: Opaque
{{- end}}

View File

@@ -1,22 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}
{{- if .Values.service.annotations }}
annotations:
{{- toYaml .Values.service.annotations | nindent 4 }}
{{- end }}
spec:
selector:
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
ports:
- name: http
port: 8080
targetPort: 8080
- name: metrics
port: 8081
targetPort: 8081
type: {{ .Values.service.type }}

View File

@@ -1,21 +0,0 @@
{{- if .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}
{{- if .Values.serviceMonitor.additionalLabels }}
{{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }}
{{- end }}
spec:
endpoints:
- honorLabels: true
path: /metrics
port: metrics
selector:
matchLabels:
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@@ -1,27 +0,0 @@
deployment:
image:
repository: ghcr.io/k8sgpt-ai/k8sgpt
tag: "" # defaults to Chart.appVersion if unspecified
imagePullPolicy: Always
annotations: {}
env:
model: "gpt-3.5-turbo"
backend: "openai" # one of: [ openai | llama ]
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "0.2"
memory: "156Mi"
secret:
secretKey: "" # base64 encoded OpenAI token
service:
type: ClusterIP
annotations: {}
serviceMonitor:
enabled: false
additionalLabels: {}

View File

@@ -51,7 +51,12 @@ var AnalyzeCmd = &cobra.Command{
os.Exit(1)
}
config.RunAnalysis()
analysisErrors := config.RunAnalysis()
if len(analysisErrors) != 0 {
for _, err := range analysisErrors {
color.Red("Error: %s", err)
}
}
if explain {
err := config.GetAIResults(output, anonymize)

View File

@@ -1,126 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"fmt"
"os"
"strings"
"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 addCmd = &cobra.Command{
Use: "add",
Short: "Configure new provider",
Long: "The new command allows to configure a new backend AI provider",
PreRun: func(cmd *cobra.Command, args []string) {
backend, _ := cmd.Flags().GetString("backend")
if strings.ToLower(backend) == "azureopenai" {
_ = cmd.MarkFlagRequired("engine")
_ = cmd.MarkFlagRequired("baseurl")
}
},
Run: func(cmd *cobra.Command, args []string) {
// get ai configuration
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
}
}
validBackend := func(validBackends []string, backend string) bool {
for _, b := range validBackends {
if b == backend {
return true
}
}
return false
}
// check if backend is not empty and a valid value
if backend == "" || !validBackend(ai.Backends, backend) {
color.Red("Error: Backend AI cannot be empty and accepted values are '%v'", strings.Join(ai.Backends, ", "))
os.Exit(1)
}
// check if model is not empty
if model == "" {
color.Red("Error: Model cannot be empty.")
os.Exit(1)
}
if ai.NeedPassword(backend) && password == "" {
fmt.Printf("Enter %s Key: ", backend)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backend,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}
// create new provider object
newProvider := ai.AIProvider{
Name: backend,
Model: model,
Password: password,
BaseURL: baseURL,
Engine: engine,
}
if providerIndex == -1 {
// provider with same name does not exist, add new provider to list
configAI.Providers = append(configAI.Providers, newProvider)
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("%s added to the AI backend provider list", backend)
} else {
// provider with same name exists, update provider info
color.Yellow("Provider with same name already exists.")
}
},
}
func init() {
// add flag for backend
addCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
// add flag for model
addCmd.Flags().StringVarP(&model, "model", "m", "gpt-3.5-turbo", "Backend AI model")
// add flag for password
addCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
// add flag for url
addCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
// add flag for azure open ai engine/deployment name
addCmd.Flags().StringVarP(&engine, "engine", "e", "", "Azure AI deployment name")
}

View File

@@ -14,8 +14,16 @@ limitations under the License.
package auth
import (
"fmt"
"os"
"strings"
"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 (
@@ -23,31 +31,90 @@ var (
password string
baseURL string
model string
engine string
)
var configAI ai.AIConfiguration
// authCmd represents the auth command
var AuthCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate with your chosen backend",
Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
// 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 ai.NeedPassword(backend) && password == "" {
fmt.Printf("Enter %s Key: ", backend)
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
color.Red("Error reading %s Key from stdin: %s", backend,
err.Error())
os.Exit(1)
}
password = strings.TrimSpace(string(bytePassword))
}
// create new provider object
newProvider := ai.AIProvider{
Name: backend,
Model: model,
Password: password,
BaseURL: baseURL,
}
if providerIndex == -1 {
// provider with same name does not exist, add new provider to list
configAI.Providers = append(configAI.Providers, newProvider)
color.Green("New provider added")
} else {
// provider with same name exists, update provider info
configAI.Providers[providerIndex] = newProvider
color.Green("Provider updated")
}
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
color.Green("key added")
},
}
func init() {
// add subcommand to list backends
AuthCmd.AddCommand(listCmd)
// add subcommand to create new backend provider
AuthCmd.AddCommand(addCmd)
// add subcommand to remove new backend provider
AuthCmd.AddCommand(removeCmd)
// add subcommand to set default backend provider
AuthCmd.AddCommand(defaultCmd)
// add flag for backend
AuthCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
// add flag for model
AuthCmd.Flags().StringVarP(&model, "model", "m", "gpt-3.5-turbo", "Backend AI model")
// add flag for password
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
// add flag for url
AuthCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
}

View File

@@ -1,79 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
providerName string
)
var defaultCmd = &cobra.Command{
Use: "default",
Short: "Set your default AI backend provider",
Long: "The command to set your new default AI backend provider (default is openai)",
Run: func(cmd *cobra.Command, args []string) {
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if providerName == "" {
if configAI.DefaultProvider != "" {
color.Yellow("Your default provider is %s", configAI.DefaultProvider)
} else {
color.Yellow("Your default provider is openai")
}
os.Exit(0)
}
// lowercase the provider name
providerName = strings.ToLower(providerName)
// Check if the provider is in the provider list
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == providerName {
providerExists = true
}
}
if !providerExists {
color.Red("Error: Provider %s does not exist", providerName)
os.Exit(1)
}
// Set the default provider
configAI.DefaultProvider = providerName
viper.Set("ai", configAI)
// Viper write config
err = viper.WriteConfig()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Print acknowledgement
color.Green("Default provider set to %s", providerName)
},
}
func init() {
// provider name flag
defaultCmd.Flags().StringVarP(&providerName, "provider", "p", "", "The name of the provider to set as default")
}

View File

@@ -1,73 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listCmd = &cobra.Command{
Use: "list",
Short: "List configured providers",
Long: "The list command displays a list of configured providers",
Run: func(cmd *cobra.Command, args []string) {
// get ai configuration
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
// Print the default if it is set
fmt.Print(color.YellowString("Default: \n"))
if configAI.DefaultProvider != "" {
fmt.Printf("> %s\n", color.BlueString(configAI.DefaultProvider))
} else {
fmt.Printf("> %s\n", color.BlueString("openai"))
}
// Get list of all AI Backends and only print htem if they are not in the provider list
fmt.Print(color.YellowString("Active: \n"))
for _, aiBackend := range ai.Backends {
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == aiBackend {
providerExists = true
}
}
if providerExists {
fmt.Printf("> %s\n", color.GreenString(aiBackend))
}
}
fmt.Print(color.YellowString("Unused: \n"))
for _, aiBackend := range ai.Backends {
providerExists := false
for _, provider := range configAI.Providers {
if provider.Name == aiBackend {
providerExists = true
}
}
if !providerExists {
fmt.Printf("> %s\n", color.RedString(aiBackend))
}
}
},
}

View File

@@ -1,71 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var removeCmd = &cobra.Command{
Use: "remove [backend(s)]",
Short: "Remove a provider",
Long: "The command to remove an AI backend provider",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
inputBackends := strings.Split(args[0], ",")
err := viper.UnmarshalKey("ai", &configAI)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if len(inputBackends) == 0 {
color.Red("Error: backend must be set.")
os.Exit(1)
}
for _, b := range inputBackends {
foundBackend := false
for i, provider := range configAI.Providers {
if b == provider.Name {
foundBackend = true
configAI.Providers = append(configAI.Providers[:i], configAI.Providers[i+1:]...)
color.Green("%s deleted to the AI backend provider list", b)
break
}
}
if !foundBackend {
color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", backend)
os.Exit(1)
}
}
viper.Set("ai", configAI)
if err := viper.WriteConfig(); err != nil {
color.Red("Error writing config file: %s", err.Error())
os.Exit(1)
}
},
}
func init() {
}

72
cmd/cache/add.go vendored
View File

@@ -1,72 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
region string
)
// addCmd represents the add command
var addCmd = &cobra.Command{
Use: "add",
Short: "Add a remote cache",
Long: `This command allows you to add a remote cache to store the results of an analysis.
The supported cache types are:
- S3`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(color.YellowString("Adding remote S3 based cache"))
// Check to see whether there is cache information already
var cacheInfo cache.CacheProvider
err := viper.UnmarshalKey("cache", &cacheInfo)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if cacheInfo.BucketName != "" {
color.Yellow("Error: a cache is already configured, please remove it first")
os.Exit(1)
}
cacheInfo.BucketName = bucketname
cacheInfo.Region = region
// Save the cache information
viper.Set("cache", cacheInfo)
err = viper.WriteConfig()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
},
}
func init() {
CacheCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&region, "region", "r", "", "The region to use for the cache")
addCmd.Flags().StringVarP(&bucketname, "bucket", "b", "", "The name of the bucket to use for the cache")
addCmd.MarkFlagRequired("bucket")
addCmd.MarkFlagRequired("region")
}

36
cmd/cache/cache.go vendored
View File

@@ -1,36 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"github.com/spf13/cobra"
)
var (
bucketname string
)
// cacheCmd represents the cache command
var CacheCmd = &cobra.Command{
Use: "cache",
Short: "For working with the cache the results of an analysis",
Long: `Cache commands allow you to add a remote cache, list the contents of the cache, and remove items from the cache.`,
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func init() {
}

53
cmd/cache/list.go vendored
View File

@@ -1,53 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/spf13/cobra"
)
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Short: "List the contents of the cache",
Long: `This command allows you to list the contents of the cache.`,
Run: func(cmd *cobra.Command, args []string) {
// load remote cache if it is configured
remoteCacheEnabled, err := cache.RemoteCacheEnabled()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
c := cache.New(false, remoteCacheEnabled)
// list the contents of the cache
names, err := c.List()
if err != nil {
panic(err)
}
for _, name := range names {
println(name)
}
},
}
func init() {
CacheCmd.AddCommand(listCmd)
}

60
cmd/cache/remove.go vendored
View File

@@ -1,60 +0,0 @@
/*
Copyright 2023 The K8sGPT Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"os"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// removeCmd represents the remove command
var removeCmd = &cobra.Command{
Use: "remove",
Short: "Remove the remote cache",
Long: `This command allows you to remove the remote cache and use the default filecache.`,
Run: func(cmd *cobra.Command, args []string) {
// Remove the remote cache
var cacheInfo cache.CacheProvider
err := viper.UnmarshalKey("cache", &cacheInfo)
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
if cacheInfo.BucketName == "" {
color.Yellow("Error: no cache is configured")
os.Exit(1)
}
// Warn user this will delete the S3 bucket and prompt them to continue
color.Yellow("Warning: this will not delete the S3 bucket %s", cacheInfo.BucketName)
cacheInfo = cache.CacheProvider{}
viper.Set("cache", cacheInfo)
err = viper.WriteConfig()
if err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
color.Green("Successfully removed the remote cache")
},
}
func init() {
CacheCmd.AddCommand(removeCmd)
}

View File

@@ -25,7 +25,7 @@ var FiltersCmd = &cobra.Command{
You can list available filters to analyze resources.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
cmd.Help()
return
}
},

View File

@@ -37,7 +37,7 @@ var listCmd = &cobra.Command{
activeFilters = coreFilters
}
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
fmt.Print(color.YellowString("Active: \n"))
fmt.Printf(color.YellowString("Active: \n"))
for _, filter := range activeFilters {
// if the filter is an integration, mark this differently
@@ -50,7 +50,7 @@ var listCmd = &cobra.Command{
// display inactive filters
if len(inactiveFilters) != 0 {
fmt.Print(color.YellowString("Unused: \n"))
fmt.Printf(color.YellowString("Unused: \n"))
for _, filter := range inactiveFilters {
// if the filter is an integration, mark this differently
if util.SliceContainsString(integrationFilters, filter) {

View File

@@ -15,10 +15,8 @@ package integration
import (
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// activateCmd represents the activate command
@@ -29,17 +27,10 @@ var activateCmd = &cobra.Command{
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
integrationName := args[0]
coreFilters, _, _ := analyzer.ListFilters()
// Update filters
activeFilters := viper.GetStringSlice("active_filters")
if len(activeFilters) == 0 {
activeFilters = coreFilters
}
integration := integration.NewIntegration()
// Check if the integation exists
err := integration.Activate(integrationName, namespace, activeFilters)
err := integration.Activate(integrationName, namespace)
if err != nil {
color.Red("Error: %v", err)
return

View File

@@ -32,7 +32,7 @@ var IntegrationCmd = &cobra.Command{
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()
cmd.Help()
},
}

View File

@@ -14,13 +14,14 @@ limitations under the License.
package cmd
import (
"fmt"
"os"
"path/filepath"
"github.com/adrg/xdg"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
"github.com/k8sgpt-ai/k8sgpt/cmd/cache"
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
"github.com/k8sgpt-ai/k8sgpt/cmd/integration"
@@ -34,9 +35,7 @@ var (
cfgFile string
kubecontext string
kubeconfig string
Version string
Commit string
Date string
version string
)
// rootCmd represents the base command when called without any subcommands
@@ -51,10 +50,8 @@ var rootCmd = &cobra.Command{
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute(v string, c string, d string) {
Version = v
Commit = c
Date = d
func Execute(v string) {
version = v
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
@@ -72,7 +69,6 @@ func init() {
rootCmd.AddCommand(generate.GenerateCmd)
rootCmd.AddCommand(integration.IntegrationCmd)
rootCmd.AddCommand(serve.ServeCmd)
rootCmd.AddCommand(cache.CacheCmd)
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", "", "Path to a kubeconfig. Only required if out-of-cluster.")
@@ -91,7 +87,7 @@ func initConfig() {
viper.SetConfigType("yaml")
viper.SetConfigName("k8sgpt")
_ = viper.SafeWriteConfig()
viper.SafeWriteConfig()
}
viper.Set("kubecontext", kubecontext)
@@ -102,7 +98,6 @@ func initConfig() {
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
_ = 1
// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
@@ -121,7 +116,14 @@ func performConfigMigrationIfNeeded() {
err = util.EnsureDirExists(configDir)
cobra.CheckErr(err)
if oldConfigExists && newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Warning: Legacy config file at `%s` detected! This file will be ignored!", oldConfig))
return
}
if oldConfigExists && !newConfigExists {
fmt.Fprintln(os.Stderr, color.RedString("Performing config file migration from `%s` to `%s`", oldConfig, newConfig))
err = os.Rename(oldConfig, newConfig)
cobra.CheckErr(err)
}

View File

@@ -21,13 +21,11 @@ import (
k8sgptserver "github.com/k8sgpt-ai/k8sgpt/pkg/server"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)
var (
port string
metricsPort string
backend string
port string
backend string
)
var ServeCmd = &cobra.Command{
@@ -49,17 +47,17 @@ var ServeCmd = &cobra.Command{
password := os.Getenv("K8SGPT_PASSWORD")
model := os.Getenv("K8SGPT_MODEL")
baseURL := os.Getenv("K8SGPT_BASEURL")
engine := os.Getenv("K8SGPT_ENGINE")
// If the envs are set, allocate in place to the aiProvider
// else exit with error
envIsSet := backend != "" || password != "" || model != ""
envIsSet := backend != "" || password != "" || model != "" || baseURL != ""
if envIsSet {
aiProvider = &ai.AIProvider{
Name: backend,
Password: password,
Model: model,
BaseURL: baseURL,
Engine: engine,
}
configAI.Providers = append(configAI.Providers, *aiProvider)
@@ -77,10 +75,10 @@ var ServeCmd = &cobra.Command{
if aiProvider == nil {
for _, provider := range configAI.Providers {
if backend == provider.Name {
// the pointer to the range variable is not really an issue here, as there
// is a break right after, but to prevent potential future issues, a temp
// variable is assigned
p := provider
// he pointer to the range variable is not really an issue here, as there
// is a break right after, but to prevent potential future issues, a temp
// variable is assigned
p := provider
aiProvider = &p
break
}
@@ -92,42 +90,23 @@ var ServeCmd = &cobra.Command{
os.Exit(1)
}
logger, err := zap.NewProduction()
server := k8sgptserver.Config{
Backend: aiProvider.Name,
Port: port,
Token: aiProvider.Password,
}
err = server.Serve()
if err != nil {
color.Red("failed to create logger: %v", err)
color.Red("Error: %v", err)
os.Exit(1)
}
defer logger.Sync()
server := k8sgptserver.Config{
Backend: aiProvider.Name,
Port: port,
MetricsPort: metricsPort,
Token: aiProvider.Password,
Logger: logger,
}
go func() {
if err := server.ServeMetrics(); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
}()
go func() {
if err := server.Serve(); err != nil {
color.Red("Error: %v", err)
os.Exit(1)
}
}()
// Wait for both servers to exit
select {}
// 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(&metricsPort, "metrics-port", "", "8081", "Port to run the metrics-server on")
ServeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
}

View File

@@ -14,9 +14,6 @@ limitations under the License.
package cmd
import (
"fmt"
"runtime/debug"
"github.com/spf13/cobra"
)
@@ -26,21 +23,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version number of k8sgpt",
Long: `All software has versions. This is k8sgpt's`,
Run: func(cmd *cobra.Command, args []string) {
if Version == "dev" {
details, ok := debug.ReadBuildInfo()
if ok && details.Main.Version != "" && details.Main.Version != "(devel)" {
Version = details.Main.Version
for _, i := range details.Settings {
if i.Key == "vcs.time" {
Date = i.Value
}
if i.Key == "vcs.revision" {
Commit = i.Value
}
}
}
}
fmt.Printf("ks8gpt: %s (%s), built at: %s\n", Version, Commit, Date)
cmd.Printf("k8sgpt version %s\n", version)
},
}

View File

@@ -9,12 +9,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM golang:1.20.4-alpine3.16 AS builder
FROM golang:1.20.3-alpine3.16 AS builder
ENV CGO_ENABLED=0
ARG VERSION
ARG COMMIT
ARG DATE
WORKDIR /workspace
COPY go.mod go.sum ./
@@ -22,7 +20,7 @@ RUN go mod download
COPY ./ ./
RUN go build -o /workspace/k8sgpt -ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" ./
RUN go build -o /workspace/k8sgpt ./
FROM gcr.io/distroless/static AS production

View File

@@ -47,7 +47,7 @@
"showLineNumbers": false,
"showMiniMap": false
},
"content": "![K8sGPT Logo](https://k8sgpt.ai/images/logo-black.png)",
"content": "![K8sGPT Logo](https://k8sgpt.ai/images/logo-white.png)",
"mode": "markdown"
},
"pluginVersion": "9.4.7",

View File

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

View File

@@ -1,10 +1,7 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ template "k8sgpt.fullname" . }}
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "k8sgpt.labels" . | nindent 4 }}
name: k8sgpt-cluster-role-all
rules:
- apiGroups:
- '*'

View File

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

View File

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

View File

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

View File

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

48
go.mod
View File

@@ -3,34 +3,26 @@ module github.com/k8sgpt-ai/k8sgpt
go 1.20
require (
github.com/aquasecurity/trivy-operator v0.13.2
github.com/aquasecurity/trivy-operator v0.13.1
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.9.3
github.com/sashabaranov/go-openai v1.9.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.3
golang.org/x/term v0.8.0
helm.sh/helm/v3 v3.12.0
k8s.io/api v0.27.2
k8s.io/apimachinery v0.27.2
k8s.io/client-go v0.27.2
k8s.io/kubectl v0.27.2
github.com/stretchr/testify v1.8.2
golang.org/x/term v0.7.0
helm.sh/helm/v3 v3.11.3
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
k8s.io/kubectl v0.26.3
)
require github.com/adrg/xdg v0.4.0
require (
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20230515081240-6b5b845c638e.1
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.30.0-20230514071713-3d78cb8bbc06.1
github.com/aws/aws-sdk-go v1.44.266
)
require github.com/jmespath/go-jmespath v0.4.0 // indirect
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -54,7 +46,7 @@ require (
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.2+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
@@ -123,7 +115,7 @@ require (
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.15.1
github.com/prometheus/client_golang v1.15.0
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
@@ -155,29 +147,29 @@ require (
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.55.0
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.27.1 // indirect
k8s.io/apiserver v0.27.1 // indirect
k8s.io/cli-runtime v0.27.2 // indirect
k8s.io/component-base v0.27.2 // 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-20230501164219-8b0f38b5fd1f // 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.13.2 // indirect
sigs.k8s.io/kustomize/kyaml v0.14.1 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

675
go.sum

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 235 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -15,12 +15,8 @@ package main
import "github.com/k8sgpt-ai/k8sgpt/cmd"
var (
version = "dev"
commit = "HEAD"
date = "unknown"
)
var version = "dev"
func main() {
cmd.Execute(version, commit, date)
cmd.Execute(version)
}

View File

@@ -1,94 +0,0 @@
package ai
import (
"context"
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"github.com/fatih/color"
"github.com/sashabaranov/go-openai"
)
type AzureAIClient struct {
client *openai.Client
language string
model string
}
func (c *AzureAIClient) Configure(config IAIConfig, lang string) error {
token := config.GetPassword()
baseURL := config.GetBaseURL()
engine := config.GetEngine()
defaultConfig := openai.DefaultAzureConfig(token, baseURL, engine)
client := openai.NewClientWithConfig(defaultConfig)
if client == nil {
return errors.New("error creating Azure OpenAI client")
}
c.language = lang
c.client = client
c.model = config.GetModel()
return nil
}
func (c *AzureAIClient) 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 *AzureAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache) (string, error) {
inputKey := strings.Join(prompt, " ")
// Check for cached data
cacheKey := util.GetCacheKey(a.GetName(), a.language, inputKey)
if !cache.IsCacheDisabled() && cache.Exists(cacheKey) {
response, err := cache.Load(cacheKey)
if err != nil {
return "", err
}
if response != "" {
output, err := base64.StdEncoding.DecodeString(response)
if err != nil {
color.Red("error decoding cached data: %v", err)
return "", nil
}
return string(output), nil
}
}
response, err := a.GetCompletion(ctx, inputKey)
if err != nil {
return "", err
}
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err != nil {
color.Red("error storing value to cache: %v", err)
return "", nil
}
return response, nil
}
func (a *AzureAIClient) GetName() string {
return "azureopenai"
}

View File

@@ -19,21 +19,6 @@ import (
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
)
var (
clients = []IAI{
&OpenAIClient{},
&AzureAIClient{},
&LocalAIClient{},
&NoOpAIClient{},
}
Backends = []string{
"openai",
"localai",
"azureopenai",
"noopai",
}
)
type IAI interface {
Configure(config IAIConfig, language string) error
GetCompletion(ctx context.Context, prompt string) (string, error)
@@ -45,30 +30,30 @@ type IAIConfig interface {
GetPassword() string
GetModel() string
GetBaseURL() string
GetEngine() string
}
func NewClient(provider string) IAI {
for _, c := range clients {
if provider == c.GetName() {
return c
}
switch provider {
case "openai":
return &OpenAIClient{}
case "localai":
return &LocalAIClient{}
case "noopai":
return &NoOpAIClient{}
default:
return &OpenAIClient{}
}
// default client
return &OpenAIClient{}
}
type AIConfiguration struct {
Providers []AIProvider `mapstructure:"providers"`
DefaultProvider string `mapstructure:"defaultprovider"`
Providers []AIProvider `mapstructure:"providers"`
}
type AIProvider struct {
Name string `mapstructure:"name"`
Model string `mapstructure:"model"`
Password string `mapstructure:"password" yaml:"password,omitempty"`
BaseURL string `mapstructure:"baseurl" yaml:"baseurl,omitempty"`
Engine string `mapstructure:"engine" yaml:"engine,omitempty"`
Password string `mapstructure:"password"`
BaseURL string `mapstructure:"baseurl"`
}
func (p *AIProvider) GetBaseURL() string {
@@ -83,10 +68,6 @@ func (p *AIProvider) GetModel() string {
return p.Model
}
func (p *AIProvider) GetEngine() string {
return p.Engine
}
func NeedPassword(backend string) bool {
return backend != "localai"
}

View File

@@ -57,7 +57,7 @@ func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, cache cache.I
return "", err
}
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
if err != nil {
color.Red("error storing value to cache: %v", err)

View File

@@ -1,9 +1,5 @@
package ai
const (
default_prompt = `Simplify the following Kubernetes error message delimited by triple dashes written in --- %s --- language; --- %s ---.
Provide the most possible solution in a step by step style in no more than 280 characters. Write the output in the following format:
Error: {Explain error here}
Solution: {Step by step solution here}
`
default_prompt = "Simplify the following Kubernetes error message and provide a solution in %s: %s"
)

View File

@@ -34,21 +34,18 @@ import (
)
type Analysis struct {
Context context.Context
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []common.Result
Errors []string
Namespace string
Cache cache.ICache
Explain bool
MaxConcurrency int
AnalysisAIProvider string // The name of the AI Provider used for this analysis
Context context.Context
Filters []string
Client *kubernetes.Client
AIClient ai.IAI
Results []common.Result
Namespace string
Cache cache.ICache
Explain bool
MaxConcurrency int
}
type AnalysisStatus string
type AnalysisErrors []string
const (
StateOK AnalysisStatus = "OK"
@@ -56,8 +53,6 @@ const (
)
type JsonOutput struct {
Provider string `json:"provider"`
Errors AnalysisErrors `json:"errors"`
Status AnalysisStatus `json:"status"`
Problems int `json:"problems"`
Results []common.Result `json:"results"`
@@ -76,12 +71,6 @@ func NewAnalysis(backend string, language string, filters []string, namespace st
os.Exit(1)
}
// Backend string will have high priority than a default provider
// Backend as "openai" represents the default CLI argument passed through
if configAI.DefaultProvider != "" && backend == "openai" {
backend = configAI.DefaultProvider
}
var aiProvider ai.AIProvider
for _, provider := range configAI.Providers {
if backend == provider.Name {
@@ -112,29 +101,22 @@ func NewAnalysis(backend string, language string, filters []string, namespace st
return nil, err
}
// load remote cache if it is configured
remoteCacheEnabled, err := cache.RemoteCacheEnabled()
if err != nil {
return nil, err
}
return &Analysis{
Context: ctx,
Filters: filters,
Client: client,
AIClient: aiClient,
Namespace: namespace,
Cache: cache.New(noCache, remoteCacheEnabled),
Explain: explain,
MaxConcurrency: maxConcurrency,
AnalysisAIProvider: backend,
Context: ctx,
Filters: filters,
Client: client,
AIClient: aiClient,
Namespace: namespace,
Cache: cache.New(noCache),
Explain: explain,
MaxConcurrency: maxConcurrency,
}, nil
}
func (a *Analysis) RunAnalysis() {
func (a *Analysis) RunAnalysis() []error {
activeFilters := viper.GetStringSlice("active_filters")
coreAnalyzerMap, analyzerMap := analyzer.GetAnalyzerMap()
analyzerMap := analyzer.GetAnalyzerMap()
analyzerConfig := common.Analyzer{
Client: a.Client,
@@ -143,12 +125,13 @@ func (a *Analysis) RunAnalysis() {
AIClient: a.AIClient,
}
var errorList []error
semaphore := make(chan struct{}, a.MaxConcurrency)
// if there are no filters selected and no active_filters then run coreAnalyzer
// if there are no filters selected and no active_filters then run all of them
if len(a.Filters) == 0 && len(activeFilters) == 0 {
var wg sync.WaitGroup
var mutex sync.Mutex
for _, analyzer := range coreAnalyzerMap {
for _, analyzer := range analyzerMap {
wg.Add(1)
semaphore <- struct{}{}
go func(analyzer common.IAnalyzer, wg *sync.WaitGroup, semaphore chan struct{}) {
@@ -156,7 +139,7 @@ func (a *Analysis) RunAnalysis() {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", reflect.TypeOf(analyzer).Name(), err))
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("[%s] %s", reflect.TypeOf(analyzer).Name(), err)))
mutex.Unlock()
}
mutex.Lock()
@@ -167,7 +150,7 @@ func (a *Analysis) RunAnalysis() {
}
wg.Wait()
return
return errorList
}
semaphore = make(chan struct{}, a.MaxConcurrency)
// if the filters flag is specified
@@ -183,7 +166,7 @@ func (a *Analysis) RunAnalysis() {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", filter, err))
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("[%s] %s", filter, err)))
mutex.Unlock()
}
mutex.Lock()
@@ -192,11 +175,11 @@ func (a *Analysis) RunAnalysis() {
<-semaphore
}(analyzer, filter)
} else {
a.Errors = append(a.Errors, fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter))
errorList = append(errorList, fmt.Errorf(fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter)))
}
}
wg.Wait()
return
return errorList
}
var wg sync.WaitGroup
@@ -212,7 +195,7 @@ func (a *Analysis) RunAnalysis() {
results, err := analyzer.Analyze(analyzerConfig)
if err != nil {
mutex.Lock()
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", filter, err))
errorList = append(errorList, fmt.Errorf("[%s] %s", filter, err))
mutex.Unlock()
}
mutex.Lock()
@@ -223,6 +206,7 @@ func (a *Analysis) RunAnalysis() {
}
}
wg.Wait()
return errorList
}
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
@@ -251,7 +235,7 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error {
// FIXME: can we avoid checking if output is json multiple times?
// maybe implement the progress bar better?
if output != "json" {
_ = bar.Exit()
bar.Exit()
}
// Check for exhaustion
@@ -272,7 +256,7 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error {
analysis.Details = parsedText
if output != "json" {
_ = bar.Add(1)
bar.Add(1)
}
a.Results[index] = analysis
}

View File

@@ -14,127 +14,14 @@ limitations under the License.
package analysis
import (
"context"
"encoding/json"
"fmt"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
"github.com/magiconair/properties/assert"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
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"
"strings"
"testing"
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/stretchr/testify/require"
)
// sub-function
func analysis_RunAnalysisFilterTester(t *testing.T, filterFlag string) []common.Result {
clientset := fake.NewSimpleClientset(
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
},
Status: v1.PodStatus{
Phase: v1.PodPending,
Conditions: []v1.PodCondition{
{
Type: v1.PodScheduled,
Reason: "Unschedulable",
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
},
},
},
},
&v1.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",
},
},
},
&networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Annotations: map[string]string{},
},
},
)
analysis := Analysis{
Context: context.Background(),
Results: []common.Result{},
Namespace: "default",
MaxConcurrency: 1,
Client: &kubernetes.Client{
Client: clientset,
},
}
if len(filterFlag) > 0 {
// `--filter` is explicitly given
analysis.Filters = strings.Split(filterFlag, ",")
}
analysis.RunAnalysis()
return analysis.Results
}
// Test: Filter logic with running different Analyzers
func TestAnalysis_RunAnalysisWithFilter(t *testing.T) {
var results []common.Result
var filterFlag string
//1. Neither --filter flag Nor active filter is specified, only the "core analyzers"
results = analysis_RunAnalysisFilterTester(t, "")
assert.Equal(t, len(results), 3) // all built-in resource will be analyzed
//2. When the --filter flag is specified
filterFlag = "Pod" // --filter=Pod
results = analysis_RunAnalysisFilterTester(t, filterFlag)
assert.Equal(t, len(results), 1)
assert.Equal(t, results[0].Kind, filterFlag)
filterFlag = "Ingress,Pod" // --filter=Ingress,Pod
results = analysis_RunAnalysisFilterTester(t, filterFlag)
assert.Equal(t, len(results), 2)
}
// Test: Filter logic with Active Filter
func TestAnalysis_RunAnalysisActiveFilter(t *testing.T) {
//When the --filter flag is not specified but has actived filter in config
var results []common.Result
viper.SetDefault("active_filters", "Ingress")
results = analysis_RunAnalysisFilterTester(t, "")
assert.Equal(t, len(results), 1)
viper.SetDefault("active_filters", []string{"Ingress", "Service"})
results = analysis_RunAnalysisFilterTester(t, "")
assert.Equal(t, len(results), 2)
viper.SetDefault("active_filters", []string{"Ingress", "Service", "Pod"})
results = analysis_RunAnalysisFilterTester(t, "")
assert.Equal(t, len(results), 3)
}
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
analysis := Analysis{
@@ -163,7 +50,6 @@ func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
fmt.Println(expected)
require.Equal(t, got, expected)
}
func TestAnalysis_ProblemJsonOutput(t *testing.T) {

View File

@@ -42,10 +42,8 @@ func (a *Analysis) jsonOutput() ([]byte, error) {
}
result := JsonOutput{
Provider: a.AnalysisAIProvider,
Problems: problems,
Results: a.Results,
Errors: a.Errors,
Status: status,
}
output, err := json.MarshalIndent(result, "", " ")
@@ -57,17 +55,6 @@ func (a *Analysis) jsonOutput() ([]byte, error) {
func (a *Analysis) textOutput() ([]byte, error) {
var output strings.Builder
// Print the AI provider used for this analysis
output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString(a.AnalysisAIProvider)))
if len(a.Errors) != 0 {
output.WriteString("\n")
output.WriteString(color.YellowString("Warnings : \n"))
for _, aerror := range a.Errors {
output.WriteString(fmt.Sprintf("- %s\n", color.YellowString(aerror)))
}
}
output.WriteString("\n")
if len(a.Results) == 0 {
output.WriteString(color.GreenString("No problems detected\n"))

View File

@@ -78,20 +78,18 @@ func ListFilters() ([]string, []string, []string) {
return coreKeys, additionalKeys, integrationAnalyzers
}
func GetAnalyzerMap() (map[string]common.IAnalyzer, map[string]common.IAnalyzer) {
func GetAnalyzerMap() map[string]common.IAnalyzer {
coreAnalyzer := make(map[string]common.IAnalyzer)
mergedAnalyzerMap := make(map[string]common.IAnalyzer)
mergedMap := make(map[string]common.IAnalyzer)
// add core analyzer
for key, value := range coreAnalyzerMap {
coreAnalyzer[key] = value
mergedAnalyzerMap[key] = value
mergedMap[key] = value
}
// add additional analyzer
for key, value := range additionalAnalyzerMap {
mergedAnalyzerMap[key] = value
mergedMap[key] = value
}
integrationProvider := integration.NewIntegration()
@@ -108,9 +106,9 @@ func GetAnalyzerMap() (map[string]common.IAnalyzer, map[string]common.IAnalyzer)
fmt.Println(color.RedString(err.Error()))
os.Exit(1)
}
in.AddAnalyzer(&mergedAnalyzerMap)
in.AddAnalyzer(&mergedMap)
}
}
return coreAnalyzer, mergedAnalyzerMap
return mergedMap
}

View File

@@ -205,15 +205,15 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
@@ -269,15 +269,15 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
@@ -333,15 +333,15 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
@@ -397,15 +397,15 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Name: "example",
Image: "nginx",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse("100m"),
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse("200m"),
"cpu": resource.MustParse("200m"),
"memory": resource.MustParse("256Mi"),
},
},
@@ -461,7 +461,7 @@ func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *test
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "example",
Name: "example",
Image: "nginx",
},
},
@@ -487,7 +487,7 @@ func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *test
var errorFound bool
for _, analysis := range analysisResults {
for _, err := range analysis.Error {
if strings.Contains(err.Text, "does not have resource configured.") {
if strings.Contains(err.Text, "does not have resource configured."){
errorFound = true
break
}

View File

@@ -58,7 +58,7 @@ func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
}
if len(failures) > 0 {
preAnalysis[node.Name] = common.PreAnalysis{
preAnalysis[fmt.Sprintf("%s", node.Name)] = common.PreAnalysis{
Node: node,
FailureDetails: failures,
}

View File

@@ -85,7 +85,7 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
}
} else {
// when pod is Running but its ReadinessProbe fails
if !containerStatus.Ready && pod.Status.Phase == "Running" {
if containerStatus.Ready == false && pod.Status.Phase == "Running" {
// parse the event log and append details
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
if err != nil || evt == nil {

29
pkg/cache/cache.go vendored
View File

@@ -1,41 +1,14 @@
package cache
import (
"github.com/spf13/viper"
)
type ICache interface {
Store(key string, data string) error
Load(key string) (string, error)
List() ([]string, error)
Exists(key string) bool
IsCacheDisabled() bool
}
func New(noCache bool, remoteCache bool) ICache {
if remoteCache {
return NewS3Cache(noCache)
}
func New(noCache bool) ICache {
return &FileBasedCache{
noCache: noCache,
}
}
// CacheProvider is the configuration for the cache provider when using a remote cache
type CacheProvider struct {
BucketName string `mapstructure:"bucketname"`
Region string `mapstructure:"region"`
}
func RemoteCacheEnabled() (bool, error) {
// load remote cache if it is configured
var cache CacheProvider
err := viper.UnmarshalKey("cache", &cache)
if err != nil {
return false, err
}
if cache.BucketName != "" && cache.Region != "" {
return true, nil
}
return false, nil
}

View File

@@ -19,25 +19,6 @@ func (f *FileBasedCache) IsCacheDisabled() bool {
return f.noCache
}
func (*FileBasedCache) List() ([]string, error) {
path, err := xdg.CacheFile("k8sgpt")
if err != nil {
return nil, err
}
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}
var result []string
for _, file := range files {
result = append(result, file.Name())
}
return result, nil
}
func (*FileBasedCache) Exists(key string) bool {
path, err := xdg.CacheFile(filepath.Join("k8sgpt", key))

118
pkg/cache/s3_based.go vendored
View File

@@ -1,118 +0,0 @@
package cache
import (
"bytes"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/spf13/viper"
)
// Generate ICache implementation
type S3Cache struct {
noCache bool
bucketName string
session *s3.S3
}
func (s *S3Cache) Store(key string, data string) error {
// Store the object as a new file in the bucket with data as the content
_, err := s.session.PutObject(&s3.PutObjectInput{
Body: aws.ReadSeekCloser(bytes.NewReader([]byte(data))),
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
})
return err
}
func (s *S3Cache) Load(key string) (string, error) {
// Retrieve the object from the bucket and load it into a string
result, err := s.session.GetObject(&s3.GetObjectInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
})
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
buf.ReadFrom(result.Body)
result.Body.Close()
return buf.String(), nil
}
func (s *S3Cache) List() ([]string, error) {
// List the files in the bucket
result, err := s.session.ListObjectsV2(&s3.ListObjectsV2Input{Bucket: aws.String(s.bucketName)})
if err != nil {
return nil, err
}
var keys []string
for _, item := range result.Contents {
keys = append(keys, *item.Key)
}
return keys, nil
}
func (s *S3Cache) Exists(key string) bool {
// Check if the object exists in the bucket
_, err := s.session.HeadObject(&s3.HeadObjectInput{
Bucket: aws.String(s.bucketName),
Key: aws.String(key),
})
return err == nil
}
func (s *S3Cache) IsCacheDisabled() bool {
return s.noCache
}
func NewS3Cache(nocache bool) ICache {
var cache CacheProvider
err := viper.UnmarshalKey("cache", &cache)
if err != nil {
panic(err)
}
if cache.BucketName == "" {
panic("Bucket name not configured")
}
if cache.Region == "" {
panic("Region not configured")
}
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
Config: aws.Config{
Region: aws.String(cache.Region),
},
}))
s := s3.New(sess)
// Check if the bucket exists, if not create it
_, err = s.HeadBucket(&s3.HeadBucketInput{
Bucket: aws.String(cache.BucketName),
})
if err != nil {
_, err = s.CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String(cache.BucketName),
})
if err != nil {
panic(err)
}
}
return &S3Cache{
noCache: nocache,
session: s,
bucketName: cache.BucketName,
}
}

View File

@@ -66,11 +66,14 @@ func (*Integration) Get(name string) (IIntegration, error) {
return integrations[name], nil
}
func (*Integration) Activate(name string, namespace string, activeFilters []string) error {
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)

View File

@@ -1,65 +0,0 @@
package server
import (
"context"
json "encoding/json"
rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
)
type handler struct {
rpc.UnimplementedServerServer
}
func (h *handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (
*schemav1.AnalyzeResponse,
error,
) {
if i.Output == "" {
i.Output = "json"
}
if i.Backend == "" {
i.Backend = "openai"
}
if int(i.MaxConcurrency) == 0 {
i.MaxConcurrency = 10
}
config, err := analysis.NewAnalysis(
i.Backend,
i.Language,
i.Filters,
i.Namespace,
i.Nocache,
i.Explain,
int(i.MaxConcurrency),
)
if err != nil {
return &schemav1.AnalyzeResponse{}, err
}
config.RunAnalysis()
if i.Explain {
err := config.GetAIResults(i.Output, i.Anonymize)
if err != nil {
return &schemav1.AnalyzeResponse{}, err
}
}
out, err := config.PrintOutput(i.Output)
if err != nil {
return &schemav1.AnalyzeResponse{}, err
}
var obj schemav1.AnalyzeResponse
err = json.Unmarshal(out, &obj)
if err != nil {
return &schemav1.AnalyzeResponse{}, err
}
return &obj, nil
}

View File

@@ -1,52 +1,77 @@
package server
import (
"context"
"fmt"
"bytes"
"net/http"
"time"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
)
func logInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
buf *bytes.Buffer
}
// Call the handler to execute the gRPC request
response, err := handler(ctx, req)
duration := time.Since(start).Milliseconds()
fields := []zap.Field{
zap.Int64("duration_ms", duration),
zap.String("method", info.FullMethod),
zap.Any("request", req),
}
// Get the remote address from the context
peer, ok := peer.FromContext(ctx)
if ok {
fields = append(fields, zap.String("remote_addr", peer.Addr.String()))
}
if err != nil {
fields = append(fields, zap.Int32("status_code", int32(status.Code(err))))
}
message := "request completed"
if err != nil {
message = fmt.Sprintf("request failed. %s", err.Error())
}
logRequest(logger, fields, int(status.Code(err)), message)
return response, err
func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
return &loggingResponseWriter{
w,
http.StatusOK,
&bytes.Buffer{},
}
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
return lrw.buf.Write(b)
}
func (lrw *loggingResponseWriter) Flush() {
if f, ok := lrw.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
lrw.ResponseWriter.Write(lrw.buf.Bytes())
}
func logRequest(logger *zap.Logger, fields []zap.Field, statusCode int, message string) {
if statusCode >= 400 {
logger.Error(message, fields...)
} else {
logger.Info(message, fields...)
logger.Info("request completed", fields...)
}
}
func loggingMiddleware(next http.Handler) http.Handler {
config := zap.NewProductionConfig()
config.DisableCaller = true
logger, err := config.Build()
if err != nil {
panic(err)
}
defer logger.Sync()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
lrw := NewLoggingResponseWriter(w)
start := time.Now()
defer func() {
duration := time.Since(start).Milliseconds()
fields := []zap.Field{
zap.Int64("duration_ms", duration),
zap.String("method", r.Method),
zap.String("remote_addr", r.RemoteAddr),
zap.Int("status_code", lrw.statusCode),
zap.String("url", r.URL.Path),
}
logRequest(logger, fields, lrw.statusCode, lrw.buf.String())
}()
next.ServeHTTP(lrw, r)
lrw.Flush()
})
}

View File

@@ -15,33 +15,23 @@ package server
import (
json "encoding/json"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
rpc "buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go/schema/v1/schemav1grpc"
"github.com/fatih/color"
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
type Config struct {
Port string
MetricsPort string
Backend string
Key string
Token string
Output string
maxConcurrency int
Handler *handler
Logger *zap.Logger
metricsServer *http.Server
}
type Health struct {
@@ -56,46 +46,74 @@ var health = Health{
Failure: 0,
}
func (s *Config) Serve() error {
var lis net.Listener
var err error
address := fmt.Sprintf(":%s", s.Port)
lis, err = net.Listen("tcp", address)
if err != nil {
return err
}
s.Logger.Info(fmt.Sprintf("binding api to %s", s.Port))
grpcServerUnaryInterceptor := grpc.UnaryInterceptor(logInterceptor(s.Logger))
grpcServer := grpc.NewServer(grpcServerUnaryInterceptor)
reflection.Register(grpcServer)
rpc.RegisterServerServer(grpcServer, s.Handler)
if err := grpcServer.Serve(
lis,
); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
type Result struct {
Analysis []analysis.Analysis `json:"analysis"`
}
func (s *Config) ServeMetrics() error {
s.Logger.Info(fmt.Sprintf("binding metrics to %s", s.MetricsPort))
s.metricsServer = &http.Server{
ReadHeaderTimeout: 3 * time.Second,
Addr: fmt.Sprintf(":%s", s.MetricsPort),
func (s *Config) analyzeHandler(w http.ResponseWriter, r *http.Request) {
namespace := r.URL.Query().Get("namespace")
explain := getBoolParam(r.URL.Query().Get("explain"))
anonymize := getBoolParam(r.URL.Query().Get("anonymize"))
nocache := getBoolParam(r.URL.Query().Get("nocache"))
language := r.URL.Query().Get("language")
var err error
s.maxConcurrency, err = strconv.Atoi(r.URL.Query().Get("maxConcurrency"))
if err != nil {
s.maxConcurrency = 10
}
s.metricsServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/healthz":
w.WriteHeader(http.StatusOK)
case "/metrics":
promhttp.Handler().ServeHTTP(w, r)
default:
w.WriteHeader(http.StatusNotFound)
s.Output = r.URL.Query().Get("output")
if s.Output == "" {
s.Output = "json"
}
config, err := analysis.NewAnalysis(s.Backend, language, []string{}, namespace, nocache, explain, s.maxConcurrency)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
analysisErrors := config.RunAnalysis()
if analysisErrors != nil {
var errorMessage string
for _, err := range analysisErrors {
errorMessage += err.Error() + "\n"
}
})
if err := s.metricsServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
http.Error(w, errorMessage, http.StatusInternalServerError)
health.Failure++
}
if explain {
err := config.GetAIResults(s.Output, anonymize)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
out, err := config.PrintOutput(s.Output)
if err != nil {
health.Failure++
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
health.Success++
fmt.Fprintf(w, string(out))
}
func (s *Config) Serve() error {
handler := loggingMiddleware(http.DefaultServeMux)
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/analyze", s.analyzeHandler)
http.HandleFunc("/healthz", s.healthzHandler)
color.Green("Starting server on port %s", s.Port)
err := http.ListenAndServe(":"+s.Port, handler)
if err != nil {
fmt.Printf("error starting server: %s\n", err)
return err
}
return nil
@@ -107,7 +125,7 @@ func (s *Config) healthzHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, string(js))
fmt.Fprintf(w, string(js))
}
func getBoolParam(param string) bool {

View File

@@ -12,6 +12,10 @@
"gomodMassage"
],
"packageRules": [
{
"matchPackageNames": ["k8s.io/client-go"],
"allowedVersions": "!/1\\.(4\\.0|5\\.0|5\\.1|5\\.2)$/"
},
{
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": "!/^0/",
@@ -44,4 +48,4 @@
]
}
]
}
}