mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 11:33:08 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d3a3933cd | ||
|
|
9da75e02bc | ||
|
|
4ce56f38b4 | ||
|
|
40b5b7e185 | ||
|
|
c55025d04e | ||
|
|
36ba6c5147 | ||
|
|
6a2f315b2f | ||
|
|
45fa827c04 | ||
|
|
4106d39c32 | ||
|
|
1979c86d0f | ||
|
|
12f764d584 | ||
|
|
85ebd12c30 | ||
|
|
5c17c24055 | ||
|
|
ce4910bc5d | ||
|
|
d8d0beef65 | ||
|
|
745e960f49 | ||
|
|
6d29fcf294 | ||
|
|
e7d41496dd | ||
|
|
e78ff05419 | ||
|
|
105a239d94 | ||
|
|
4de989c803 | ||
|
|
a7e9b486ba | ||
|
|
a77426593d | ||
|
|
526e22f88b | ||
|
|
4314804ca7 | ||
|
|
035348d8a0 | ||
|
|
70c68929d8 | ||
|
|
b17fd7c986 | ||
|
|
2f0f2dfa8a | ||
|
|
3e7cea7bd3 | ||
|
|
fcd29a547d | ||
|
|
3d0ba3e78c | ||
|
|
91613baa5c | ||
|
|
6eb8f6793e | ||
|
|
e5cc4a28cb | ||
|
|
eac9f07abf | ||
|
|
130e4c2efd | ||
|
|
93b5ca1985 | ||
|
|
aa057565b5 | ||
|
|
13d64a5875 | ||
|
|
03b63befa2 | ||
|
|
3c6c7597e0 | ||
|
|
71f36bdb0b | ||
|
|
d6fb648e23 | ||
|
|
343aec8f04 | ||
|
|
78f7f2ba85 | ||
|
|
a8e1932122 | ||
|
|
390f309088 | ||
|
|
5d54c3f840 | ||
|
|
4a7bad313b | ||
|
|
be4b0bb3c2 | ||
|
|
1b386f64f2 | ||
|
|
8dea6170a2 | ||
|
|
928b39a728 | ||
|
|
c23f24de2e | ||
|
|
ff4aaf7c32 | ||
|
|
2c28c555cf | ||
|
|
d00ed33678 | ||
|
|
6473a2b532 | ||
|
|
1d196286b7 | ||
|
|
71ae5a7301 | ||
|
|
eb32a0f2bc | ||
|
|
ec08cac214 | ||
|
|
dca5b4710d | ||
|
|
b1c791a396 |
4
.github/workflows/build_container.yaml
vendored
4
.github/workflows/build_container.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
- "**.md"
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.20"
|
||||
GO_VERSION: "~1.21"
|
||||
IMAGE_NAME: "k8sgpt"
|
||||
defaults:
|
||||
run:
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
outputs: type=docker,dest=/tmp/${{ env.IMAGE_NAME }}-image.tar
|
||||
|
||||
- name: Upload image as artifact
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
|
||||
uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4
|
||||
with:
|
||||
name: ${{ env.IMAGE_NAME }}-image.tar
|
||||
path: /tmp/${{ env.IMAGE_NAME }}-image.tar
|
||||
|
||||
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
|
||||
- uses: google-github-actions/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3.7.13
|
||||
- uses: google-github-actions/release-please-action@cc61a07e2da466bebbc19b3a7dd01d6aecb20d1e # v4.0.2
|
||||
id: release
|
||||
with:
|
||||
command: manifest
|
||||
@@ -45,11 +45,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
with:
|
||||
go-version: '1.20'
|
||||
go-version: '1.21'
|
||||
- name: Download Syft
|
||||
uses: anchore/sbom-action/download-syft@fd74a6fb98a204a1ad35bbfae0122c1a302ff88b # v0.15.0
|
||||
uses: anchore/sbom-action/download-syft@c7f031d9249a826a082ea14c79d3b686a51d485a # v0.15.3
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5
|
||||
with:
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@fd74a6fb98a204a1ad35bbfae0122c1a302ff88b # v0.15.0
|
||||
uses: anchore/sbom-action@c7f031d9249a826a082ea14c79d3b686a51d485a # v0.15.3
|
||||
with:
|
||||
image: ${{ env.IMAGE_TAG }}
|
||||
artifact-name: sbom-${{ env.IMAGE_NAME }}
|
||||
|
||||
12
.github/workflows/test.yaml
vendored
12
.github/workflows/test.yaml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.20"
|
||||
GO_VERSION: "~1.21"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -19,13 +19,9 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
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: Run test
|
||||
run: go test ./...
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.3.22"}
|
||||
{".":"0.3.26"}
|
||||
123
CHANGELOG.md
123
CHANGELOG.md
@@ -1,5 +1,128 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.26](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.25...v0.3.26) (2024-01-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial Prometheus analyzers ([#855](https://github.com/k8sgpt-ai/k8sgpt/issues/855)) ([45fa827](https://github.com/k8sgpt-ai/k8sgpt/commit/45fa827c046b91d901a08bec1a892d9c0917f350))
|
||||
* interactive mode ([#854](https://github.com/k8sgpt-ai/k8sgpt/issues/854)) ([9da75e0](https://github.com/k8sgpt-ai/k8sgpt/commit/9da75e02bc17146898377e4f90b7f59c5a8e0eee))
|
||||
* unify aiClientName const for all providers ([#848](https://github.com/k8sgpt-ai/k8sgpt/issues/848)) ([5c17c24](https://github.com/k8sgpt-ai/k8sgpt/commit/5c17c240550609d9fb7771fe67fe1ab19660b4da))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.16 ([#847](https://github.com/k8sgpt-ai/k8sgpt/issues/847)) ([ce4910b](https://github.com/k8sgpt-ai/k8sgpt/commit/ce4910bc5d064f80076877d7a096fff903308b63))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.17 ([#852](https://github.com/k8sgpt-ai/k8sgpt/issues/852)) ([85ebd12](https://github.com/k8sgpt-ai/k8sgpt/commit/85ebd12c30d369c5ef9a42b5a834d091523a7b6e))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.18 ([#856](https://github.com/k8sgpt-ai/k8sgpt/issues/856)) ([4106d39](https://github.com/k8sgpt-ai/k8sgpt/commit/4106d39c322940413ebfd9ac0bf6f5bd31830e93))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.19 ([#859](https://github.com/k8sgpt-ai/k8sgpt/issues/859)) ([6a2f315](https://github.com/k8sgpt-ai/k8sgpt/commit/6a2f315b2f4344f2924b7915e8a1393f9732a1e9))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.17.11 ([#853](https://github.com/k8sgpt-ai/k8sgpt/issues/853)) ([1979c86](https://github.com/k8sgpt-ai/k8sgpt/commit/1979c86d0f59921d55cd4229a37d604a6f1dc578))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.17.11 ([#861](https://github.com/k8sgpt-ai/k8sgpt/issues/861)) ([40b5b7e](https://github.com/k8sgpt-ai/k8sgpt/commit/40b5b7e185c8d335bdefb131988b9900ad26bac3))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#864](https://github.com/k8sgpt-ai/k8sgpt/issues/864)) ([36ba6c5](https://github.com/k8sgpt-ai/k8sgpt/commit/36ba6c5147a9ed75c14dbba4bc06cae903e651a4))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#865](https://github.com/k8sgpt-ai/k8sgpt/issues/865)) ([c55025d](https://github.com/k8sgpt-ai/k8sgpt/commit/c55025d04ebf9da0f6092aabb0b043ccef05164c))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update actions/upload-artifact digest to 1eb3cb2 ([#867](https://github.com/k8sgpt-ai/k8sgpt/issues/867)) ([4ce56f3](https://github.com/k8sgpt-ai/k8sgpt/commit/4ce56f38b4338a6a2fe69f588b0f17e0b54d0ae6))
|
||||
* **deps:** update anchore/sbom-action action to v0.15.3 ([#850](https://github.com/k8sgpt-ai/k8sgpt/issues/850)) ([12f764d](https://github.com/k8sgpt-ai/k8sgpt/commit/12f764d5846accbd987d40f69a153dceb9954f39))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* adjusted README information about providers ([#844](https://github.com/k8sgpt-ai/k8sgpt/issues/844)) ([745e960](https://github.com/k8sgpt-ai/k8sgpt/commit/745e960f492e6dd0e50aa4a1ce7239c677025024))
|
||||
|
||||
## [0.3.25](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.24...v0.3.25) (2024-01-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added Google GenAI client; simplified IAI/clients API surface. ([#829](https://github.com/k8sgpt-ai/k8sgpt/issues/829)) ([e7d4149](https://github.com/k8sgpt-ai/k8sgpt/commit/e7d41496ddaa145c70079852da8b2ce3b3b7289f))
|
||||
* code_cov badge ([#821](https://github.com/k8sgpt-ai/k8sgpt/issues/821)) ([fcd29a5](https://github.com/k8sgpt-ai/k8sgpt/commit/fcd29a547d73ba48935762e2f568f5755f5c6ed3))
|
||||
* coverage reports ([#819](https://github.com/k8sgpt-ai/k8sgpt/issues/819)) ([3d0ba3e](https://github.com/k8sgpt-ai/k8sgpt/commit/3d0ba3e78cabaf5f1262c5b5b16ebabad974fa87))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.10 ([#811](https://github.com/k8sgpt-ai/k8sgpt/issues/811)) ([e5cc4a2](https://github.com/k8sgpt-ai/k8sgpt/commit/e5cc4a28cb3682e7094e6ceddf91b65da991ddb6))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.12 ([#813](https://github.com/k8sgpt-ai/k8sgpt/issues/813)) ([91613ba](https://github.com/k8sgpt-ai/k8sgpt/commit/91613baa5cc5244c93deb344abcdd905802eef30))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.14 ([#822](https://github.com/k8sgpt-ai/k8sgpt/issues/822)) ([526e22f](https://github.com/k8sgpt-ai/k8sgpt/commit/526e22f88b8de15eceb10965b045ef0366ff2d6c))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.15 ([#835](https://github.com/k8sgpt-ai/k8sgpt/issues/835)) ([e78ff05](https://github.com/k8sgpt-ai/k8sgpt/commit/e78ff054190cd54cabe17d77ac69443e517f1e55))
|
||||
* **deps:** update module github.com/prometheus/client_golang to v1.18.0 ([#814](https://github.com/k8sgpt-ai/k8sgpt/issues/814)) ([6eb8f67](https://github.com/k8sgpt-ai/k8sgpt/commit/6eb8f6793ed989ba3ac7ed00336345f68b09bf45))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.17.10 ([#824](https://github.com/k8sgpt-ai/k8sgpt/issues/824)) ([4314804](https://github.com/k8sgpt-ai/k8sgpt/commit/4314804ca7e782f5149dc2078ba9c859edc4688a))
|
||||
* **deps:** update module golang.org/x/term to v0.16.0 ([#831](https://github.com/k8sgpt-ai/k8sgpt/issues/831)) ([4de989c](https://github.com/k8sgpt-ai/k8sgpt/commit/4de989c803ee43a02d75112d1b3a54daee3dd9af))
|
||||
* **deps:** update module google.golang.org/api to v0.155.0 ([#836](https://github.com/k8sgpt-ai/k8sgpt/issues/836)) ([105a239](https://github.com/k8sgpt-ai/k8sgpt/commit/105a239d94384f4096c01d9978564040773ab56e))
|
||||
* no explain case, improved readability. ([#825](https://github.com/k8sgpt-ai/k8sgpt/issues/825)) ([035348d](https://github.com/k8sgpt-ai/k8sgpt/commit/035348d8a0d290ac26b42425945eaafe038cedc5))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* added basic server startup test ([#817](https://github.com/k8sgpt-ai/k8sgpt/issues/817)) ([3e7cea7](https://github.com/k8sgpt-ai/k8sgpt/commit/3e7cea7bd39253718bc3d2f8b10ac5fc9b98cbc2))
|
||||
* **deps:** pin codecov/codecov-action action to eaaf4be ([#820](https://github.com/k8sgpt-ai/k8sgpt/issues/820)) ([2f0f2df](https://github.com/k8sgpt-ai/k8sgpt/commit/2f0f2dfa8a5957cb8b10864c14d7883158723a6a))
|
||||
* **deps:** update anchore/sbom-action action to v0.15.2 ([#823](https://github.com/k8sgpt-ai/k8sgpt/issues/823)) ([70c6892](https://github.com/k8sgpt-ai/k8sgpt/commit/70c68929d8d963c0bd17390c76e366d4339f56b9))
|
||||
* lint fixes ([#833](https://github.com/k8sgpt-ai/k8sgpt/issues/833)) ([a7e9b48](https://github.com/k8sgpt-ai/k8sgpt/commit/a7e9b486bad7c2d62878e470a755d1fef3803680))
|
||||
* remove code cov ([#832](https://github.com/k8sgpt-ai/k8sgpt/issues/832)) ([a774265](https://github.com/k8sgpt-ai/k8sgpt/commit/a77426593d7f3a8cfa810336ff08a2266db7fb4f))
|
||||
|
||||
|
||||
### Dependency Updates
|
||||
|
||||
* go module bump to fix CVE: GHSA-45x7-px36-x8w8 & GHSA-7ww5-4wqc-m92c ([#810](https://github.com/k8sgpt-ai/k8sgpt/issues/810)) ([b17fd7c](https://github.com/k8sgpt-ai/k8sgpt/commit/b17fd7c98644afa70d414fcb32e49e61e1c831ad))
|
||||
|
||||
## [0.3.24](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.23...v0.3.24) (2023-12-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add last termination state when pod is in CrashloopBackoff ([#792](https://github.com/k8sgpt-ai/k8sgpt/issues/792)) ([ff4aaf7](https://github.com/k8sgpt-ai/k8sgpt/commit/ff4aaf7c328a58fcad8e4fb0f93ea543725eedd5))
|
||||
* Add license scan report and status ([#796](https://github.com/k8sgpt-ai/k8sgpt/issues/796)) ([343aec8](https://github.com/k8sgpt-ai/k8sgpt/commit/343aec8f0455c9461eb8d495ca5bd446b4bad667))
|
||||
* version upgrade to 1.21 ([#798](https://github.com/k8sgpt-ai/k8sgpt/issues/798)) ([c23f24d](https://github.com/k8sgpt-ai/k8sgpt/commit/c23f24de2e79347e4f5465e28af34e138cc13231))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* added the ability to set the trivy variables by the user ([#797](https://github.com/k8sgpt-ai/k8sgpt/issues/797)) ([928b39a](https://github.com/k8sgpt-ai/k8sgpt/commit/928b39a7283ee274dd517e727624eceb3795594d))
|
||||
* **deps:** update module cloud.google.com/go/storage to v1.36.0 ([#805](https://github.com/k8sgpt-ai/k8sgpt/issues/805)) ([390f309](https://github.com/k8sgpt-ai/k8sgpt/commit/390f30908800dfe21e2c1660139b0bd9d36b34d6))
|
||||
* **deps:** update module github.com/aquasecurity/trivy-operator to v0.17.1 ([#780](https://github.com/k8sgpt-ai/k8sgpt/issues/780)) ([71f36bd](https://github.com/k8sgpt-ai/k8sgpt/commit/71f36bdb0b3729c4357299b7d03829dd5b6a69ec))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.6 ([#783](https://github.com/k8sgpt-ai/k8sgpt/issues/783)) ([1b386f6](https://github.com/k8sgpt-ai/k8sgpt/commit/1b386f64f2863d8a49f423ad571cba009807bc55))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.7 ([#804](https://github.com/k8sgpt-ai/k8sgpt/issues/804)) ([3c6c759](https://github.com/k8sgpt-ai/k8sgpt/commit/3c6c7597e014bfd68794b1764c3a8902e8a798ea))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.8 ([#807](https://github.com/k8sgpt-ai/k8sgpt/issues/807)) ([93b5ca1](https://github.com/k8sgpt-ai/k8sgpt/commit/93b5ca1985c3730592388ba6fc32ecca9b806888))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.49.9 ([#808](https://github.com/k8sgpt-ai/k8sgpt/issues/808)) ([130e4c2](https://github.com/k8sgpt-ai/k8sgpt/commit/130e4c2efd0e5b34cdc84c357c6c1f3987cf7c35))
|
||||
* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.2.1 ([#801](https://github.com/k8sgpt-ai/k8sgpt/issues/801)) ([aa05756](https://github.com/k8sgpt-ai/k8sgpt/commit/aa057565b5c971c493443f3ede4aed8f8a6399f7))
|
||||
* **deps:** update module github.com/mittwald/go-helm-client to v0.12.5 ([#802](https://github.com/k8sgpt-ai/k8sgpt/issues/802)) ([4a7bad3](https://github.com/k8sgpt-ai/k8sgpt/commit/4a7bad313b66750bd830413b7fef005580ad843c))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.17.9 ([#772](https://github.com/k8sgpt-ai/k8sgpt/issues/772)) ([13d64a5](https://github.com/k8sgpt-ai/k8sgpt/commit/13d64a58750c7262c07042b557fbf2c4a511b777))
|
||||
* **deps:** update module github.com/spf13/viper to v1.18.2 ([#787](https://github.com/k8sgpt-ai/k8sgpt/issues/787)) ([8dea617](https://github.com/k8sgpt-ai/k8sgpt/commit/8dea6170a2c00c03f08f25e4f0a232be617536f1))
|
||||
* **deps:** update module google.golang.org/api to v0.154.0 ([#779](https://github.com/k8sgpt-ai/k8sgpt/issues/779)) ([78f7f2b](https://github.com/k8sgpt-ai/k8sgpt/commit/78f7f2ba85fd357cab13ccc15e9e767e8611773a))
|
||||
* **deps:** update module google.golang.org/grpc to v1.60.1 ([#790](https://github.com/k8sgpt-ai/k8sgpt/issues/790)) ([5d54c3f](https://github.com/k8sgpt-ai/k8sgpt/commit/5d54c3f840a9ce002606b6601187e69fb62f8a28))
|
||||
* **deps:** update module helm.sh/helm/v3 to v3.13.3 ([#803](https://github.com/k8sgpt-ai/k8sgpt/issues/803)) ([a8e1932](https://github.com/k8sgpt-ai/k8sgpt/commit/a8e193212222811f3a278df6056dd2165c4323bd))
|
||||
* lowercase logs before running regex matching in LogAnalyzer ([#794](https://github.com/k8sgpt-ai/k8sgpt/issues/794)) ([03b63be](https://github.com/k8sgpt-ai/k8sgpt/commit/03b63befa247ac84b795a0ec8d5280196b8d570d))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update actions/setup-go action to v5 ([#788](https://github.com/k8sgpt-ai/k8sgpt/issues/788)) ([d00ed33](https://github.com/k8sgpt-ai/k8sgpt/commit/d00ed33678b1560a3996f1d735d84ca0ca05c0b0))
|
||||
* **deps:** update actions/upload-artifact action to v4 ([#806](https://github.com/k8sgpt-ai/k8sgpt/issues/806)) ([d6fb648](https://github.com/k8sgpt-ai/k8sgpt/commit/d6fb648e23c1ed1e4680fc4b7b4e96501f50ad48))
|
||||
* **deps:** update anchore/sbom-action action to v0.15.1 ([#784](https://github.com/k8sgpt-ai/k8sgpt/issues/784)) ([6473a2b](https://github.com/k8sgpt-ai/k8sgpt/commit/6473a2b532491b707b3af922fc2198e626ebf219))
|
||||
* **deps:** update google-github-actions/release-please-action action to v4 ([#782](https://github.com/k8sgpt-ai/k8sgpt/issues/782)) ([2c28c55](https://github.com/k8sgpt-ai/k8sgpt/commit/2c28c555cf4e891b90ebd9e9eae1cd8724e9886f))
|
||||
* **deps:** update google-github-actions/release-please-action action to v4.0.2 ([#800](https://github.com/k8sgpt-ai/k8sgpt/issues/800)) ([be4b0bb](https://github.com/k8sgpt-ai/k8sgpt/commit/be4b0bb3c24e04d35f40d16fd8e94ddbc8457ca6))
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* replace rest client with controller-runtime clientset for Trivy analyzers ([#776](https://github.com/k8sgpt-ai/k8sgpt/issues/776)) ([1d19628](https://github.com/k8sgpt-ai/k8sgpt/commit/1d196286b75f0ea6c068e8bdb01455fb36c52432))
|
||||
|
||||
## [0.3.23](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.22...v0.3.23) (2023-11-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Gateway analysers ([#764](https://github.com/k8sgpt-ai/k8sgpt/issues/764)) ([ec08cac](https://github.com/k8sgpt-ai/k8sgpt/commit/ec08cac21496b34b123b75b06d9283eb6539e890))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.48.3 ([#768](https://github.com/k8sgpt-ai/k8sgpt/issues/768)) ([b1c791a](https://github.com/k8sgpt-ai/k8sgpt/commit/b1c791a396b7287ef916e8f8d382a0e14ba39949))
|
||||
* **deps:** update module github.com/mittwald/go-helm-client to v0.12.4 ([#767](https://github.com/k8sgpt-ai/k8sgpt/issues/767)) ([dca5b47](https://github.com/k8sgpt-ai/k8sgpt/commit/dca5b4710d1bb35dfc3346219d3bddb7c726300e))
|
||||
|
||||
## [0.3.22](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.21...v0.3.22) (2023-11-21)
|
||||
|
||||
|
||||
|
||||
228
README.md
228
README.md
@@ -9,6 +9,8 @@
|
||||

|
||||
[](https://bestpractices.coreinfrastructure.org/projects/7272)
|
||||
[](https://docs.k8sgpt.ai/)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt?ref=badge_shield)
|
||||
[](https://codecov.io/github/k8sgpt-ai/k8sgpt)
|
||||
|
||||
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
|
||||
|
||||
@@ -36,7 +38,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.22/k8sgpt_386.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/k8sgpt_386.rpm
|
||||
sudo rpm -ivh k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -45,7 +47,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.22/k8sgpt_amd64.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh -i k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -57,7 +59,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.22/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -65,7 +67,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.22/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -78,14 +80,14 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.22/k8sgpt_386.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/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.22/k8sgpt_amd64.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.26/k8sgpt_amd64.apk
|
||||
apk add k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->x
|
||||
@@ -123,14 +125,14 @@ _This mode of operation is ideal for continuous monitoring of your cluster and c
|
||||
|
||||
## Quick Start
|
||||
|
||||
* Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)
|
||||
* 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.
|
||||
* 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.
|
||||
* You also run `k8sgpt analyze --with-doc` (with or without the explain flag) to get the official documentation from kubernetes.
|
||||
* You also run `k8sgpt analyze --with-doc` (with or without the explain flag) to get the official documentation from Kubernetes.
|
||||
|
||||
## Analyzers
|
||||
|
||||
@@ -159,6 +161,9 @@ you will be able to write your own analyzers.
|
||||
- [x] hpaAnalyzer
|
||||
- [x] pdbAnalyzer
|
||||
- [x] networkPolicyAnalyzer
|
||||
- [x] gatewayClass
|
||||
- [x] gateway
|
||||
- [x] httproute
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -282,204 +287,32 @@ k8sgpt serve
|
||||
_Analysis with serve mode_
|
||||
|
||||
```
|
||||
curl -X GET "http://localhost:8080/analyze?namespace=k8sgpt&explain=false"
|
||||
grpcurl -plaintext -d '{"namespace": "k8sgpt", "explain": false}' localhost:8080 schema.v1.ServerService/Analyze
|
||||
```
|
||||
</details>
|
||||
|
||||
## LLM AI Backends
|
||||
|
||||
## Key Features
|
||||
K8sGPT uses the chosen LLM, generative AI provider when you want to explain the analysis results using --explain flag e.g. `k8sgpt analyze --explain`. You can use `--backend` flag to specify a configured provider (it's `openai` by default).
|
||||
|
||||
<details>
|
||||
<summary> LocalAI provider </summary>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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).
|
||||
|
||||
### Start the API server
|
||||
|
||||
To start the API server, follow the instruction in [LocalAI](https://github.com/go-skynet/LocalAI#example-use-gpt4all-j-model).
|
||||
|
||||
### Run k8sgpt
|
||||
|
||||
To run k8sgpt, run `k8sgpt auth add` with the `localai` backend:
|
||||
You can list available providers using `k8sgpt auth list`:
|
||||
|
||||
```
|
||||
k8sgpt auth add --backend localai --model <model_name> --baseurl http://localhost:8080/v1 --temperature 0.7
|
||||
```
|
||||
|
||||
Now you can analyze with the `localai` backend:
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --backend localai
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary> AzureOpenAI provider </summary>
|
||||
|
||||
<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>Cohere provider</summary>
|
||||
|
||||
<em>Prerequisites:</em> a Cohere API key is needed, please visit the [Cohere dashboard](https://dashboard.cohere.ai/api-keys) to create one.
|
||||
|
||||
To run k8sgpt, run `k8sgpt auth` with the `cohere` backend:
|
||||
|
||||
```
|
||||
k8sgpt auth add --backend cohere --model command-nightly
|
||||
```
|
||||
|
||||
Lastly, enter your Cohere API key, after the prompt.
|
||||
|
||||
Now you are ready to analyze with the Cohere backend:
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --backend cohere
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Amazon Bedrock provider</summary>
|
||||
|
||||
|
||||
<em>Prerequisites</em>
|
||||
Bedrock API access is needed.
|
||||
|
||||
<img src="images/bedrock.png" width="500px;" />
|
||||
|
||||
As illustrated below, you will need to enable this in the [AWS Console](https://eu-central-1.console.aws.amazon.com/bedrock/home?region=eu-central-1#/modelaccess)
|
||||
|
||||
In addition to this you will need to set the follow local environmental variables:
|
||||
|
||||
|
||||
```
|
||||
- AWS_ACCESS_KEY
|
||||
- AWS_SECRET_ACCESS_KEY
|
||||
- AWS_DEFAULT_REGION
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
k8sgpt auth add --backend amazonbedrock --model anthropic.claude-v2
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
```
|
||||
k8sgpt analyze -e -b amazonbedrock
|
||||
|
||||
0 argocd/argocd-application-controller(argocd-application-controller)
|
||||
- Error: StatefulSet uses the service argocd/argocd-application-controller which does not exist.
|
||||
|
||||
You're right, I don't have enough context to determine if a StatefulSet is correctly configured to use a non-existent service. A StatefulSet manages Pods with persistent storage, and the Pods are created from the same spec. The service name referenced in the StatefulSet configuration would need to match an existing Kubernetes service for the Pods to connect to. Without more details on the specific StatefulSet and environment, I can't confirm whether the configuration is valid or not.
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Amazon SageMaker Provider</summary>
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
1. **AWS CLI Configuration**: Make sure you have the AWS Command Line Interface (CLI) configured on your machine. If you haven't already configured the AWS CLI, you can follow the official AWS documentation for instructions on how to do it: [AWS CLI Configuration Guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html).
|
||||
|
||||
2. **SageMaker Instance**: You need to have an Amazon SageMaker instance set up. If you don't have one already, you can follow the step-by-step instructions provided in this repository for creating a SageMaker instance: [llm-sagemaker-jumpstart-cdk](https://github.com/zaremb/llm-sagemaker-jumpstart-cdk).
|
||||
|
||||
#### Backend Configuration
|
||||
|
||||
To add amazonsagemaker backend two parameters are required:
|
||||
|
||||
* `--endpointname` Amazon SageMaker endpoint name.
|
||||
* `--providerRegion` AWS region where SageMaker instance is created. `k8sgpt` uses this region to connect to SageMaker (not the one defined with AWS CLI or environment variables )
|
||||
|
||||
To add amazonsagemaker as a backend run:
|
||||
|
||||
```bash
|
||||
k8sgpt auth add --backend amazonsagemaker --providerRegion eu-west-1 --endpointname endpoint-xxxxxxxxxx
|
||||
```
|
||||
|
||||
#### Optional params
|
||||
|
||||
Optionally, when adding the backend and later by changing the configuration file, you can set the following parameters:
|
||||
|
||||
`-l, --maxtokens int` Specify a maximum output length. Adjust (1-...) to control text length. Higher values produce longer output, lower values limit length (default 2048)
|
||||
|
||||
`-t, --temperature float32` The sampling temperature, value ranges between 0 ( output be more deterministic) and 1 (more random) (default 0.7)
|
||||
|
||||
`-c, --topp float32` Probability Cutoff: Set a threshold (0.0-1.0) to limit word choices. Higher values add randomness, lower values increase predictability. (default 0.5)
|
||||
|
||||
To make amazonsagemaker as a default backend run:
|
||||
|
||||
```bash
|
||||
k8sgpt auth default -p amazonsagemaker
|
||||
```
|
||||
|
||||
#### AmazonSageMaker Usage
|
||||
|
||||
```bash
|
||||
./k8sgpt analyze -e -b amazonsagemaker
|
||||
100% |███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| (1/1, 14 it/min)
|
||||
AI Provider: amazonsagemaker
|
||||
|
||||
0 default/nginx(nginx)
|
||||
- Error: Back-off pulling image "nginxx"
|
||||
Error: Back-off pulling image "nginxx"
|
||||
|
||||
Solution:
|
||||
|
||||
1. Check if the image exists in the registry by running `docker image ls nginxx`.
|
||||
2. If the image is not found, try pulling it by running `docker pull nginxx`.
|
||||
3. If the image is still not available, check if there are any network issues by running `docker network inspect` and `docker network list`.
|
||||
4. If the issue persists, try restarting the Docker daemon by running `sudo service docker restart`.
|
||||
```
|
||||
|
||||
</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:
|
||||
Default:
|
||||
> openai
|
||||
Active:
|
||||
Active:
|
||||
Unused:
|
||||
> openai
|
||||
> azureopenai
|
||||
Unused:
|
||||
> localai
|
||||
> noopai
|
||||
> amazonbedrock
|
||||
> azureopenai
|
||||
> cohere
|
||||
|
||||
> amazonbedrock
|
||||
> amazonsagemaker
|
||||
> google
|
||||
> noopai
|
||||
```
|
||||
|
||||
For detailed documentation on how to configure and use each provider see [here](https://docs.k8sgpt.ai/reference/providers/backend/).
|
||||
|
||||
_To set a new default provider_
|
||||
|
||||
@@ -488,15 +321,12 @@ k8sgpt auth default -p azureopenai
|
||||
Default provider set to azureopenai
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
## Key Features
|
||||
|
||||
<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:
|
||||
@@ -640,3 +470,7 @@ Find us on [Slack](https://join.slack.com/t/k8sgpt/shared_invite/zt-276pa9uyq-px
|
||||
<a href="https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt" />
|
||||
</a>
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt?ref=badge_large)
|
||||
|
||||
@@ -16,23 +16,27 @@ package analyze
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai/interactive"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
explain bool
|
||||
backend string
|
||||
output string
|
||||
filters []string
|
||||
language string
|
||||
nocache bool
|
||||
namespace string
|
||||
anonymize bool
|
||||
maxConcurrency int
|
||||
withDoc bool
|
||||
explain bool
|
||||
backend string
|
||||
output string
|
||||
filters []string
|
||||
language string
|
||||
nocache bool
|
||||
namespace string
|
||||
anonymize bool
|
||||
maxConcurrency int
|
||||
withDoc bool
|
||||
interactiveMode bool
|
||||
)
|
||||
|
||||
// AnalyzeCmd represents the problems command
|
||||
@@ -43,37 +47,68 @@ var AnalyzeCmd = &cobra.Command{
|
||||
Long: `This command will find problems within your Kubernetes cluster and
|
||||
provide you with a list of issues that need to be resolved`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// AnalysisResult configuration
|
||||
config, err := analysis.NewAnalysis(backend,
|
||||
language, filters, namespace, nocache, explain, maxConcurrency, withDoc)
|
||||
// Create analysis configuration first.
|
||||
config, err := analysis.NewAnalysis(
|
||||
backend,
|
||||
language,
|
||||
filters,
|
||||
namespace,
|
||||
nocache,
|
||||
explain,
|
||||
maxConcurrency,
|
||||
withDoc,
|
||||
interactiveMode,
|
||||
)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
config.RunAnalysis()
|
||||
|
||||
if explain {
|
||||
err := config.GetAIResults(output, anonymize)
|
||||
if err != nil {
|
||||
if err := config.GetAIResults(output, anonymize); err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// print results
|
||||
output, err := config.PrintOutput(output)
|
||||
output_data, err := config.PrintOutput(output)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
fmt.Println(string(output_data))
|
||||
|
||||
if interactiveMode && explain {
|
||||
if output == "json" {
|
||||
color.Yellow("Caution: interactive mode using --json enabled may use additional tokens.")
|
||||
}
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
interactiveClient := interactive.NewInteractionRunner(config, output_data)
|
||||
|
||||
go interactiveClient.StartInteraction()
|
||||
for {
|
||||
select {
|
||||
case res := <-sigs:
|
||||
switch res {
|
||||
default:
|
||||
os.Exit(0)
|
||||
}
|
||||
case res := <-interactiveClient.State:
|
||||
switch res {
|
||||
case interactive.E_EXITED:
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
// namespace flag
|
||||
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
|
||||
// no cache flag
|
||||
@@ -94,4 +129,6 @@ func init() {
|
||||
AnalyzeCmd.Flags().IntVarP(&maxConcurrency, "max-concurrency", "m", 10, "Maximum number of concurrent requests to the Kubernetes API server")
|
||||
// kubernetes doc flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&withDoc, "with-doc", "d", false, "Give me the official documentation of the involved field")
|
||||
// interactive mode flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Enable interactive mode that allows further conversation with LLM about the problem. Works only with --explain flag")
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func init() {
|
||||
// add flag for url
|
||||
addCmd.Flags().StringVarP(&baseURL, "baseurl", "u", "", "URL AI provider, (e.g `http://localhost:8080/v1`)")
|
||||
// add flag for endpointName
|
||||
addCmd.Flags().StringVarP(&endpointName, "endpointname", "n", "", "Endpoint Name, (e.g `endpoint-xxxxxxxxxxxx`)")
|
||||
addCmd.Flags().StringVarP(&endpointName, "endpointname", "n", "", "Endpoint Name, e.g. `endpoint-xxxxxxxxxxxx` (only for amazonbedrock, amazonsagemaker backends)")
|
||||
// add flag for topP
|
||||
addCmd.Flags().Float32VarP(&topP, "topp", "c", 0.5, "Probability Cutoff: Set a threshold (0.0-1.0) to limit word choices. Higher values add randomness, lower values increase predictability.")
|
||||
// max tokens
|
||||
@@ -157,7 +157,7 @@ func init() {
|
||||
// add flag for temperature
|
||||
addCmd.Flags().Float32VarP(&temperature, "temperature", "t", 0.7, "The sampling temperature, value ranges between 0 ( output be more deterministic) and 1 (more random)")
|
||||
// add flag for azure open ai engine/deployment name
|
||||
addCmd.Flags().StringVarP(&engine, "engine", "e", "", "Azure AI deployment name")
|
||||
addCmd.Flags().StringVarP(&engine, "engine", "e", "", "Azure AI deployment name (only for azureopenai backend)")
|
||||
//add flag for amazonbedrock region name
|
||||
addCmd.Flags().StringVarP(&providerRegion, "providerRegion", "r", "", "Provider Region name")
|
||||
addCmd.Flags().StringVarP(&providerRegion, "providerRegion", "r", "", "Provider Region name (only for amazonbedrock backend)")
|
||||
}
|
||||
|
||||
5
cmd/cache/cache.go
vendored
5
cmd/cache/cache.go
vendored
@@ -28,7 +28,10 @@ var CacheCmd = &cobra.Command{
|
||||
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()
|
||||
err := cmd.Help()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@ package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -85,6 +86,6 @@ func printInstructions(isGui bool, backendType string) {
|
||||
color.Green("Please open: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
|
||||
fmt.Println("")
|
||||
}
|
||||
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
|
||||
color.Green("Please copy the generated key and run `k8sgpt auth add` to add it to your config file")
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# 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.21-alpine3.19 AS builder
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
ARG VERSION
|
||||
@@ -36,4 +36,4 @@ WORKDIR /
|
||||
COPY --from=builder /workspace/k8sgpt .
|
||||
USER 65532:65532
|
||||
|
||||
ENTRYPOINT ["/k8sgpt"]
|
||||
ENTRYPOINT ["/k8sgpt"]
|
||||
|
||||
176
go.mod
176
go.mod
@@ -1,23 +1,24 @@
|
||||
module github.com/k8sgpt-ai/k8sgpt
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/trivy-operator v0.16.4
|
||||
github.com/aquasecurity/trivy-operator v0.17.1
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mittwald/go-helm-client v0.12.3
|
||||
github.com/sashabaranov/go-openai v1.17.8
|
||||
github.com/mittwald/go-helm-client v0.12.5
|
||||
github.com/prometheus/prometheus v1.8.2-0.20211119115433-692a54649ed7
|
||||
github.com/sashabaranov/go-openai v1.17.11
|
||||
github.com/schollz/progressbar/v3 v3.14.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.17.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/term v0.14.0
|
||||
helm.sh/helm/v3 v3.13.2
|
||||
golang.org/x/term v0.16.0
|
||||
helm.sh/helm/v3 v3.13.3
|
||||
k8s.io/api v0.28.4
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/client-go v0.28.4
|
||||
k8s.io/kubectl v0.28.4
|
||||
k8s.io/kubectl v0.28.4 // indirect
|
||||
|
||||
)
|
||||
|
||||
@@ -26,48 +27,78 @@ require github.com/adrg/xdg v0.4.0
|
||||
require (
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20231116211251-9f5041346631.2
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.28.1-20231116211251-9f5041346631.4
|
||||
cloud.google.com/go/storage v1.35.1
|
||||
cloud.google.com/go/storage v1.36.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0
|
||||
github.com/aws/aws-sdk-go v1.48.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
|
||||
github.com/aws/aws-sdk-go v1.49.19
|
||||
github.com/cohere-ai/cohere-go v0.2.0
|
||||
github.com/google/generative-ai-go v0.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
google.golang.org/api v0.151.0
|
||||
github.com/pterm/pterm v0.12.74
|
||||
google.golang.org/api v0.155.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
sigs.k8s.io/gateway-api v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.8 // indirect
|
||||
cloud.google.com/go/compute v1.23.1 // indirect
|
||||
atomicgo.dev/cursor v0.2.0 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/ai v0.3.0 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.3 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect
|
||||
github.com/cohere-ai/tokenizer v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
|
||||
gopkg.in/evanphx/json-patch.v5 v5.7.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
@@ -75,36 +106,36 @@ require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/aquasecurity/defsec v0.93.1 // indirect
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20230830122616-841bc0f812c7 // indirect
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20231030050624-4548cca9a5c9 // indirect
|
||||
github.com/aquasecurity/table v1.8.0 // indirect
|
||||
github.com/aquasecurity/tml v0.6.1 // indirect
|
||||
github.com/aquasecurity/trivy v0.45.1 // indirect
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20230831170347-f732860d4917 // indirect
|
||||
github.com/aquasecurity/trivy v0.47.0 // indirect
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20231020043206-3770774790ce // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd v1.7.6 // indirect
|
||||
github.com/containerd/containerd v1.7.11 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v24.0.6+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
@@ -114,7 +145,7 @@ require (
|
||||
github.com/google/go-containerregistry v0.16.1 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
@@ -122,12 +153,12 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
@@ -136,8 +167,7 @@ require (
|
||||
github.com/masahiro331/go-xfs-filesystem v0.0.0-20230608043311-a335f4599b70 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
@@ -157,10 +187,10 @@ 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.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rubenv/sql-migrate v1.5.2 // indirect
|
||||
@@ -168,49 +198,47 @@ require (
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spdx/tools-golang v0.5.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spdx/tools-golang v0.5.3 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
go.starlark.net v0.0.0-20231016134836-22325403fcb3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.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/grpc v1.59.0
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.31.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.28.3 // indirect
|
||||
k8s.io/apiserver v0.28.3 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.28.4
|
||||
k8s.io/apiserver v0.28.4 // indirect
|
||||
k8s.io/cli-runtime v0.28.4 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
|
||||
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
oras.land/oras-go v1.2.4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.15.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.15.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB |
@@ -2,25 +2,21 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/bedrockruntime"
|
||||
)
|
||||
|
||||
const amazonbedrockAIClientName = "amazonbedrock"
|
||||
|
||||
// AmazonBedRockClient represents the client for interacting with the Amazon Bedrock service.
|
||||
type AmazonBedRockClient struct {
|
||||
nopCloser
|
||||
|
||||
client *bedrockruntime.BedrockRuntime
|
||||
language string
|
||||
model string
|
||||
temperature float32
|
||||
}
|
||||
@@ -91,8 +87,8 @@ func GetRegionOrDefault(region string) string {
|
||||
return BEDROCK_DEFAULT_REGION
|
||||
}
|
||||
|
||||
// Configure configures the AmazonBedRockClient with the provided configuration and language.
|
||||
func (a *AmazonBedRockClient) Configure(config IAIConfig, language string) error {
|
||||
// Configure configures the AmazonBedRockClient with the provided configuration.
|
||||
func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
|
||||
// Create a new AWS session
|
||||
providerRegion := GetRegionOrDefault(config.GetProviderRegion())
|
||||
@@ -107,7 +103,6 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig, language string) error
|
||||
|
||||
// Create a new BedrockRuntime client
|
||||
a.client = bedrockruntime.New(sess)
|
||||
a.language = language
|
||||
a.model = GetModelOrDefault(config.GetModel())
|
||||
a.temperature = config.GetTemperature()
|
||||
|
||||
@@ -115,7 +110,7 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig, language string) error
|
||||
}
|
||||
|
||||
// GetCompletion sends a request to the model for generating completion based on the provided prompt.
|
||||
func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error) {
|
||||
func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
|
||||
// Prepare the input data for the model invocation
|
||||
request := map[string]interface{}{
|
||||
@@ -152,45 +147,7 @@ func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string,
|
||||
return output.Completion, nil
|
||||
}
|
||||
|
||||
// Parse generates a completion for the provided prompt using the Amazon Bedrock model.
|
||||
func (a *AmazonBedRockClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (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, promptTmpl)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GetName returns the name of the AmazonBedRockClient.
|
||||
func (a *AmazonBedRockClient) GetName() string {
|
||||
return "amazonbedrock"
|
||||
return amazonbedrockAIClientName
|
||||
}
|
||||
|
||||
@@ -15,24 +15,20 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/sagemakerruntime"
|
||||
)
|
||||
|
||||
const amazonsagemakerAIClientName = "amazonsagemaker"
|
||||
|
||||
type SageMakerAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *sagemakerruntime.SageMakerRuntime
|
||||
language string
|
||||
model string
|
||||
temperature float32
|
||||
endpoint string
|
||||
@@ -63,7 +59,7 @@ type Parameters struct {
|
||||
Temperature float64 `json:"temperature"`
|
||||
}
|
||||
|
||||
func (c *SageMakerAIClient) Configure(config IAIConfig, language string) error {
|
||||
func (c *SageMakerAIClient) Configure(config IAIConfig) error {
|
||||
|
||||
// Create a new AWS session
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
@@ -71,7 +67,6 @@ func (c *SageMakerAIClient) Configure(config IAIConfig, language string) error {
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
|
||||
c.language = language
|
||||
// Create a new SageMaker runtime client
|
||||
c.client = sagemakerruntime.New(sess)
|
||||
c.model = config.GetModel()
|
||||
@@ -82,18 +77,13 @@ func (c *SageMakerAIClient) Configure(config IAIConfig, language string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SageMakerAIClient) GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error) {
|
||||
func (c *SageMakerAIClient) GetCompletion(_ context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
|
||||
if len(promptTmpl) == 0 {
|
||||
promptTmpl = PromptMap["default"]
|
||||
}
|
||||
|
||||
request := Request{
|
||||
Inputs: [][]Message{
|
||||
{
|
||||
{Role: "system", Content: "DEFAULT_PROMPT"},
|
||||
{Role: "user", Content: fmt.Sprintf(promptTmpl, c.language, prompt)},
|
||||
{Role: "user", Content: prompt},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -142,29 +132,6 @@ func (c *SageMakerAIClient) GetCompletion(ctx context.Context, prompt string, pr
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (a *SageMakerAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (string, error) {
|
||||
// parse the text with the AI backend
|
||||
inputKey := strings.Join(prompt, " ")
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
|
||||
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
|
||||
|
||||
response, err := a.GetCompletion(ctx, inputKey, promptTmpl)
|
||||
if err != nil {
|
||||
color.Red("error getting completion: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
|
||||
if err != nil {
|
||||
color.Red("error storing value to cache: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *SageMakerAIClient) GetName() string {
|
||||
return "amazonsagemaker"
|
||||
func (c *SageMakerAIClient) GetName() string {
|
||||
return amazonsagemakerAIClientName
|
||||
}
|
||||
|
||||
@@ -2,27 +2,22 @@ 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"
|
||||
)
|
||||
|
||||
const azureAIClientName = "azureopenai"
|
||||
|
||||
type AzureAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *openai.Client
|
||||
language string
|
||||
model string
|
||||
temperature float32
|
||||
}
|
||||
|
||||
func (c *AzureAIClient) Configure(config IAIConfig, lang string) error {
|
||||
func (c *AzureAIClient) Configure(config IAIConfig) error {
|
||||
token := config.GetPassword()
|
||||
baseURL := config.GetBaseURL()
|
||||
engine := config.GetEngine()
|
||||
@@ -40,21 +35,20 @@ func (c *AzureAIClient) Configure(config IAIConfig, lang string) error {
|
||||
if client == nil {
|
||||
return errors.New("error creating Azure OpenAI client")
|
||||
}
|
||||
c.language = lang
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AzureAIClient) GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error) {
|
||||
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: openai.ChatMessageRoleUser,
|
||||
Content: fmt.Sprintf(default_prompt, c.language, prompt),
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
Temperature: c.temperature,
|
||||
@@ -65,42 +59,6 @@ func (c *AzureAIClient) GetCompletion(ctx context.Context, prompt string, prompt
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
func (a *AzureAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (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, promptTmpl)
|
||||
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"
|
||||
func (c *AzureAIClient) GetName() string {
|
||||
return azureAIClientName
|
||||
}
|
||||
|
||||
@@ -15,26 +15,22 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cohere-ai/cohere-go"
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
)
|
||||
|
||||
const cohereAIClientName = "cohere"
|
||||
|
||||
type CohereClient struct {
|
||||
nopCloser
|
||||
|
||||
client *cohere.Client
|
||||
language string
|
||||
model string
|
||||
temperature float32
|
||||
}
|
||||
|
||||
func (c *CohereClient) Configure(config IAIConfig, language string) error {
|
||||
func (c *CohereClient) Configure(config IAIConfig) error {
|
||||
token := config.GetPassword()
|
||||
|
||||
client, err := cohere.CreateClient(token)
|
||||
@@ -50,21 +46,17 @@ func (c *CohereClient) Configure(config IAIConfig, language string) error {
|
||||
if client == nil {
|
||||
return errors.New("error creating Cohere client")
|
||||
}
|
||||
c.language = language
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CohereClient) GetCompletion(ctx context.Context, prompt, promptTmpl string) (string, error) {
|
||||
func (c *CohereClient) GetCompletion(_ context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
if len(promptTmpl) == 0 {
|
||||
promptTmpl = PromptMap["default"]
|
||||
}
|
||||
resp, err := c.client.Generate(cohere.GenerateOptions{
|
||||
Model: c.model,
|
||||
Prompt: fmt.Sprintf(strings.TrimSpace(promptTmpl), c.language, prompt),
|
||||
Prompt: prompt,
|
||||
MaxTokens: cohere.Uint(2048),
|
||||
Temperature: cohere.Float64(float64(c.temperature)),
|
||||
K: cohere.Int(0),
|
||||
@@ -77,42 +69,6 @@ func (c *CohereClient) GetCompletion(ctx context.Context, prompt, promptTmpl str
|
||||
return resp.Generations[0].Text, nil
|
||||
}
|
||||
|
||||
func (a *CohereClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (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, promptTmpl)
|
||||
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 *CohereClient) GetName() string {
|
||||
return "cohere"
|
||||
func (c *CohereClient) GetName() string {
|
||||
return cohereAIClientName
|
||||
}
|
||||
|
||||
119
pkg/ai/googlegenai.go
Normal file
119
pkg/ai/googlegenai.go
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
const googleAIClientName = "google"
|
||||
|
||||
type GoogleGenAIClient struct {
|
||||
client *genai.Client
|
||||
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
func (c *GoogleGenAIClient) Configure(config IAIConfig) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Access your API key as an environment variable (see "Set up your API key" above)
|
||||
token := config.GetPassword()
|
||||
authOption := option.WithAPIKey(token)
|
||||
if token[0] == '{' {
|
||||
authOption = option.WithCredentialsJSON([]byte(token))
|
||||
}
|
||||
|
||||
client, err := genai.NewClient(ctx, authOption)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating genai Google SDK client: %w", err)
|
||||
}
|
||||
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GoogleGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Available models are at https://ai.google.dev/models e.g.gemini-pro.
|
||||
model := c.client.GenerativeModel(c.model)
|
||||
model.SetTemperature(c.temperature)
|
||||
model.SetTopP(c.topP)
|
||||
model.SetMaxOutputTokens(int32(c.maxTokens))
|
||||
|
||||
// Google AI SDK is capable of different inputs than just text, for now set explicit text prompt type.
|
||||
// Similarly, we could stream the response. For now k8sgpt does not support streaming.
|
||||
resp, err := model.GenerateContent(ctx, genai.Text(prompt))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(resp.Candidates) == 0 {
|
||||
if resp.PromptFeedback.BlockReason == genai.BlockReasonSafety {
|
||||
for _, r := range resp.PromptFeedback.SafetyRatings {
|
||||
if !r.Blocked {
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("complection blocked due to %v with probability %v", r.Category.String(), r.Probability.String())
|
||||
}
|
||||
}
|
||||
return "", errors.New("no complection returned; unknown reason")
|
||||
}
|
||||
|
||||
// Format output.
|
||||
// TODO(bwplotka): Provider richer output in certain cases e.g. suddenly finished
|
||||
// completion based on finish reasons or safety rankings.
|
||||
got := resp.Candidates[0]
|
||||
var output string
|
||||
for _, part := range got.Content.Parts {
|
||||
switch o := part.(type) {
|
||||
case genai.Text:
|
||||
output += string(o)
|
||||
output += "\n"
|
||||
default:
|
||||
color.Yellow("found unsupported AI response part of type %T; ignoring", part)
|
||||
}
|
||||
}
|
||||
|
||||
if got.CitationMetadata != nil && len(got.CitationMetadata.CitationSources) > 0 {
|
||||
output += "Citations:\n"
|
||||
for _, source := range got.CitationMetadata.CitationSources {
|
||||
// TODO(bwplotka): Give details around what exactly words could be attributed to the citation.
|
||||
output += fmt.Sprintf("* %s, %s\n", *source.URI, source.License)
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c *GoogleGenAIClient) GetName() string {
|
||||
return googleAIClientName
|
||||
}
|
||||
|
||||
func (c *GoogleGenAIClient) Close() {
|
||||
if err := c.client.Close(); err != nil {
|
||||
color.Red("googleai client close error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -28,25 +26,38 @@ var (
|
||||
&CohereClient{},
|
||||
&AmazonBedRockClient{},
|
||||
&SageMakerAIClient{},
|
||||
&GoogleGenAIClient{},
|
||||
}
|
||||
Backends = []string{
|
||||
"openai",
|
||||
"localai",
|
||||
"azureopenai",
|
||||
"noopai",
|
||||
"cohere",
|
||||
"amazonbedrock",
|
||||
"amazonsagemaker",
|
||||
openAIClientName,
|
||||
localAIClientName,
|
||||
azureAIClientName,
|
||||
cohereAIClientName,
|
||||
amazonbedrockAIClientName,
|
||||
amazonsagemakerAIClientName,
|
||||
googleAIClientName,
|
||||
noopAIClientName,
|
||||
}
|
||||
)
|
||||
|
||||
// IAI is an interface all clients (representing backends) share.
|
||||
type IAI interface {
|
||||
Configure(config IAIConfig, language string) error
|
||||
GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error)
|
||||
Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (string, error)
|
||||
// Configure sets up client for given configuration. This is expected to be
|
||||
// executed once per client life-time (e.g. analysis CLI command invocation).
|
||||
Configure(config IAIConfig) error
|
||||
// GetCompletion generates text based on prompt.
|
||||
GetCompletion(ctx context.Context, prompt string) (string, error)
|
||||
// GetName returns name of the backend/client.
|
||||
GetName() string
|
||||
// Close cleans all the resources. No other methods should be used on the
|
||||
// objects after this method is invoked.
|
||||
Close()
|
||||
}
|
||||
|
||||
type nopCloser struct{}
|
||||
|
||||
func (nopCloser) Close() {}
|
||||
|
||||
type IAIConfig interface {
|
||||
GetPassword() string
|
||||
GetModel() string
|
||||
|
||||
67
pkg/ai/interactive/interactive.go
Normal file
67
pkg/ai/interactive/interactive.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package interactive
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
|
||||
type INTERACTIVE_STATE int
|
||||
|
||||
const (
|
||||
prompt = "Given the following context: "
|
||||
)
|
||||
|
||||
const (
|
||||
E_RUNNING INTERACTIVE_STATE = iota
|
||||
E_EXITED = iota
|
||||
)
|
||||
|
||||
type InteractionRunner struct {
|
||||
config *analysis.Analysis
|
||||
State chan INTERACTIVE_STATE
|
||||
contextWindow []byte
|
||||
}
|
||||
|
||||
func NewInteractionRunner(config *analysis.Analysis, contextWindow []byte) *InteractionRunner {
|
||||
return &InteractionRunner{
|
||||
config: config,
|
||||
contextWindow: contextWindow,
|
||||
State: make(chan INTERACTIVE_STATE),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InteractionRunner) StartInteraction() {
|
||||
a.State <- E_RUNNING
|
||||
pterm.Println("Interactive mode enabled [type exit to close.]")
|
||||
for {
|
||||
|
||||
query := pterm.DefaultInteractiveTextInput.WithMultiLine(false)
|
||||
queryString, err := query.Show()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if queryString == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(queryString, "exit") {
|
||||
a.State <- E_EXITED
|
||||
continue
|
||||
}
|
||||
pterm.Println()
|
||||
contextWindow := fmt.Sprintf("%s %s %s", prompt, string(a.contextWindow),
|
||||
queryString)
|
||||
|
||||
response, err := a.config.AIClient.GetCompletion(a.config.Context,
|
||||
contextWindow)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
a.State <- E_EXITED
|
||||
continue
|
||||
}
|
||||
pterm.Println(response)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package ai
|
||||
|
||||
const localAIClientName = "localai"
|
||||
|
||||
type LocalAIClient struct {
|
||||
OpenAIClient
|
||||
}
|
||||
|
||||
func (a *LocalAIClient) GetName() string {
|
||||
return "localai"
|
||||
return localAIClientName
|
||||
}
|
||||
|
||||
@@ -15,58 +15,23 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
)
|
||||
|
||||
const noopAIClientName = "noopai"
|
||||
|
||||
type NoOpAIClient struct {
|
||||
client string
|
||||
language string
|
||||
model string
|
||||
nopCloser
|
||||
}
|
||||
|
||||
func (c *NoOpAIClient) Configure(config IAIConfig, language string) error {
|
||||
token := config.GetPassword()
|
||||
c.language = language
|
||||
c.client = fmt.Sprintf("I am a noop client with the token %s ", token)
|
||||
c.model = config.GetModel()
|
||||
func (c *NoOpAIClient) Configure(_ IAIConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NoOpAIClient) GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error) {
|
||||
// Create a completion request
|
||||
func (c *NoOpAIClient) GetCompletion(_ context.Context, prompt string) (string, error) {
|
||||
response := "I am a noop response to the prompt " + prompt
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (string, error) {
|
||||
// parse the text with the AI backend
|
||||
inputKey := strings.Join(prompt, " ")
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
|
||||
cacheKey := util.GetCacheKey(a.GetName(), a.language, sEnc)
|
||||
|
||||
response, err := a.GetCompletion(ctx, inputKey, promptTmpl)
|
||||
if err != nil {
|
||||
color.Red("error getting completion: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
|
||||
if err != nil {
|
||||
color.Red("error storing value to cache: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *NoOpAIClient) GetName() string {
|
||||
return "noopai"
|
||||
func (c *NoOpAIClient) GetName() string {
|
||||
return noopAIClientName
|
||||
}
|
||||
|
||||
@@ -15,22 +15,17 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
const openAIClientName = "openai"
|
||||
|
||||
type OpenAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *openai.Client
|
||||
language string
|
||||
model string
|
||||
temperature float32
|
||||
}
|
||||
@@ -43,7 +38,7 @@ const (
|
||||
topP = 1.0
|
||||
)
|
||||
|
||||
func (c *OpenAIClient) Configure(config IAIConfig, language string) error {
|
||||
func (c *OpenAIClient) Configure(config IAIConfig) error {
|
||||
token := config.GetPassword()
|
||||
defaultConfig := openai.DefaultConfig(token)
|
||||
|
||||
@@ -56,24 +51,20 @@ func (c *OpenAIClient) Configure(config IAIConfig, language string) error {
|
||||
if client == nil {
|
||||
return errors.New("error creating OpenAI client")
|
||||
}
|
||||
c.language = language
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string, promptTmpl string) (string, error) {
|
||||
func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
if len(promptTmpl) == 0 {
|
||||
promptTmpl = PromptMap["default"]
|
||||
}
|
||||
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
|
||||
Model: c.model,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: fmt.Sprintf(promptTmpl, c.language, prompt),
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
Temperature: c.temperature,
|
||||
@@ -88,42 +79,6 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string, promptT
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, cache cache.ICache, promptTmpl string) (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, promptTmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
|
||||
if err != nil {
|
||||
color.Red("error storing value to cache: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *OpenAIClient) GetName() string {
|
||||
return "openai"
|
||||
func (c *OpenAIClient) GetName() string {
|
||||
return openAIClientName
|
||||
}
|
||||
|
||||
@@ -8,10 +8,52 @@ const (
|
||||
`
|
||||
trivy_vuln_prompt = "Explain the following trivy scan result and the detail risk or root cause of the CVE ID, then provide a solution. Response in %s: %s"
|
||||
trivy_conf_prompt = "Explain the following trivy scan result and the detail risk or root cause of the security check, then provide a solution."
|
||||
|
||||
prom_conf_prompt = `Simplify the following Prometheus error message delimited by triple dashes written in --- %s --- language; --- %s ---.
|
||||
This error came when validating the Prometheus configuration file.
|
||||
Provide step by step instructions to fix, with suggestions, referencing Prometheus documentation if relevant.
|
||||
Write the output in the following format in no more than 300 characters:
|
||||
Error: {Explain error here}
|
||||
Solution: {Step by step solution here}
|
||||
`
|
||||
|
||||
prom_relabel_prompt = `
|
||||
Return your prompt in this language: %s, beginning with
|
||||
The following is a list of the form:
|
||||
job_name:
|
||||
{Prometheus job_name}
|
||||
relabel_configs:
|
||||
{Prometheus relabel_configs}
|
||||
kubernetes_sd_configs:
|
||||
{Prometheus service discovery config}
|
||||
---
|
||||
%s
|
||||
---
|
||||
For each job_name, describe the Kubernetes service and pod labels,
|
||||
namespaces, ports, and containers they match.
|
||||
Return the message:
|
||||
Discovered and parsed Prometheus scrape configurations.
|
||||
For targets to be scraped by Prometheus, ensure they are running with
|
||||
at least one of the following label sets:
|
||||
Then for each job, write this format:
|
||||
- Job: {job_name}
|
||||
- Service Labels:
|
||||
- {list of service labels}
|
||||
- Pod Labels:
|
||||
- {list of pod labels}
|
||||
- Namespaces:
|
||||
- {list of namespaces}
|
||||
- Ports:
|
||||
- {list of ports}
|
||||
- Containers:
|
||||
- {list of container names}
|
||||
`
|
||||
)
|
||||
|
||||
var PromptMap = map[string]string{
|
||||
"default": default_prompt,
|
||||
"VulnerabilityReport": trivy_vuln_prompt, // for Trivy integration, the key should match `Result.Kind` in pkg/common/types.go
|
||||
"ConfigAuditReport": trivy_conf_prompt,
|
||||
"default": default_prompt,
|
||||
"VulnerabilityReport": trivy_vuln_prompt, // for Trivy integration, the key should match `Result.Kind` in pkg/common/types.go
|
||||
"ConfigAuditReport": trivy_conf_prompt,
|
||||
"PrometheusConfigValidate": prom_conf_prompt,
|
||||
"PrometheusConfigRelabelReport": prom_relabel_prompt,
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ package analysis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -38,6 +38,7 @@ type Analysis struct {
|
||||
Context context.Context
|
||||
Filters []string
|
||||
Client *kubernetes.Client
|
||||
Language string
|
||||
AIClient ai.IAI
|
||||
Results []common.Result
|
||||
Errors []string
|
||||
@@ -65,17 +66,58 @@ type JsonOutput struct {
|
||||
Results []common.Result `json:"results"`
|
||||
}
|
||||
|
||||
func NewAnalysis(backend string, language string, filters []string, namespace string, noCache bool, explain bool, maxConcurrency int, withDoc bool) (*Analysis, error) {
|
||||
var configAI ai.AIConfiguration
|
||||
err := viper.UnmarshalKey("ai", &configAI)
|
||||
func NewAnalysis(
|
||||
backend string,
|
||||
language string,
|
||||
filters []string,
|
||||
namespace string,
|
||||
noCache bool,
|
||||
explain bool,
|
||||
maxConcurrency int,
|
||||
withDoc bool,
|
||||
interactiveMode bool,
|
||||
) (*Analysis, error) {
|
||||
// Get kubernetes client from viper.
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
kubeconfig := viper.GetString("kubeconfig")
|
||||
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
return nil, fmt.Errorf("initialising kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
if len(configAI.Providers) == 0 && explain {
|
||||
color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth")
|
||||
os.Exit(1)
|
||||
// Load remote cache if it is configured.
|
||||
cache, err := cache.GetCacheConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if noCache {
|
||||
cache.DisableCache()
|
||||
}
|
||||
|
||||
a := &Analysis{
|
||||
Context: context.Background(),
|
||||
Filters: filters,
|
||||
Client: client,
|
||||
Language: language,
|
||||
Namespace: namespace,
|
||||
Cache: cache,
|
||||
Explain: explain,
|
||||
MaxConcurrency: maxConcurrency,
|
||||
WithDoc: withDoc,
|
||||
}
|
||||
if !explain {
|
||||
// Return early if AI use was not requested.
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var configAI ai.AIConfiguration
|
||||
if err := viper.UnmarshalKey("ai", &configAI); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(configAI.Providers) == 0 {
|
||||
return nil, errors.New("AI provider not specified in configuration. Please run k8sgpt auth")
|
||||
}
|
||||
|
||||
// Backend string will have high priority than a default provider
|
||||
@@ -93,49 +135,16 @@ func NewAnalysis(backend string, language string, filters []string, namespace st
|
||||
}
|
||||
|
||||
if aiProvider.Name == "" {
|
||||
color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
|
||||
return nil, errors.New("AI provider not specified in configuration")
|
||||
return nil, fmt.Errorf("AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
|
||||
}
|
||||
|
||||
aiClient := ai.NewClient(aiProvider.Name)
|
||||
if err := aiClient.Configure(&aiProvider, language); err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
if err := aiClient.Configure(&aiProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// Get kubernetes client from viper
|
||||
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
kubeconfig := viper.GetString("kubeconfig")
|
||||
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
|
||||
if err != nil {
|
||||
color.Red("Error initialising kubernetes client: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load remote cache if it is configured
|
||||
cache, err := cache.GetCacheConfiguration()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if noCache {
|
||||
cache.DisableCache()
|
||||
}
|
||||
|
||||
return &Analysis{
|
||||
Context: ctx,
|
||||
Filters: filters,
|
||||
Client: client,
|
||||
AIClient: aiClient,
|
||||
Namespace: namespace,
|
||||
Cache: cache,
|
||||
Explain: explain,
|
||||
MaxConcurrency: maxConcurrency,
|
||||
AnalysisAIProvider: backend,
|
||||
WithDoc: withDoc,
|
||||
}, nil
|
||||
a.AIClient = aiClient
|
||||
a.AnalysisAIProvider = aiProvider.Name
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Analysis) RunAnalysis() {
|
||||
@@ -265,14 +274,14 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error {
|
||||
}
|
||||
texts = append(texts, failure.Text)
|
||||
}
|
||||
// If the resource `Kind` comes from a "integration plugin", maybe a customized prompt template will be involved.
|
||||
var promptTemplate string
|
||||
|
||||
promptTemplate := ai.PromptMap["default"]
|
||||
// If the resource `Kind` comes from an "integration plugin",
|
||||
// maybe a customized prompt template will be involved.
|
||||
if prompt, ok := ai.PromptMap[analysis.Kind]; ok {
|
||||
promptTemplate = prompt
|
||||
} else {
|
||||
promptTemplate = ai.PromptMap["default"]
|
||||
}
|
||||
parsedText, err := a.AIClient.Parse(a.Context, texts, a.Cache, promptTemplate)
|
||||
result, err := a.getAIResultForSanitizedFailures(texts, promptTemplate)
|
||||
if err != nil {
|
||||
// FIXME: can we avoid checking if output is json multiple times?
|
||||
// maybe implement the progress bar better?
|
||||
@@ -280,23 +289,22 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error {
|
||||
_ = bar.Exit()
|
||||
}
|
||||
|
||||
// Check for exhaustion
|
||||
// Check for exhaustion.
|
||||
if strings.Contains(err.Error(), "status code: 429") {
|
||||
return fmt.Errorf("exhausted API quota for AI provider %s: %v", a.AIClient.GetName(), err)
|
||||
} else {
|
||||
return fmt.Errorf("failed while calling AI provider %s: %v", a.AIClient.GetName(), err)
|
||||
}
|
||||
return fmt.Errorf("failed while calling AI provider %s: %v", a.AIClient.GetName(), err)
|
||||
}
|
||||
|
||||
if anonymize {
|
||||
for _, failure := range analysis.Error {
|
||||
for _, s := range failure.Sensitive {
|
||||
parsedText = strings.ReplaceAll(parsedText, s.Masked, s.Unmasked)
|
||||
result = strings.ReplaceAll(result, s.Masked, s.Unmasked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analysis.Details = parsedText
|
||||
analysis.Details = result
|
||||
if output != "json" {
|
||||
_ = bar.Add(1)
|
||||
}
|
||||
@@ -304,3 +312,44 @@ func (a *Analysis) GetAIResults(output string, anonymize bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analysis) getAIResultForSanitizedFailures(texts []string, promptTmpl string) (string, error) {
|
||||
inputKey := strings.Join(texts, " ")
|
||||
// Check for cached data.
|
||||
// TODO(bwplotka): This might depend on model too (or even other client configuration pieces), fix it in later PRs.
|
||||
cacheKey := util.GetCacheKey(a.AIClient.GetName(), a.Language, inputKey)
|
||||
|
||||
if !a.Cache.IsCacheDisabled() && a.Cache.Exists(cacheKey) {
|
||||
response, err := a.Cache.Load(cacheKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if response != "" {
|
||||
output, err := base64.StdEncoding.DecodeString(response)
|
||||
if err == nil {
|
||||
return string(output), nil
|
||||
}
|
||||
color.Red("error decoding cached data; ignoring cache item: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Process template.
|
||||
prompt := fmt.Sprintf(strings.TrimSpace(promptTmpl), a.Language, inputKey)
|
||||
response, err := a.AIClient.GetCompletion(a.Context, prompt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.Cache.Store(cacheKey, base64.StdEncoding.EncodeToString([]byte(response))); err != nil {
|
||||
color.Red("error storing value to cache; value won't be cached: %v", err)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *Analysis) Close() {
|
||||
if a.AIClient == nil {
|
||||
return
|
||||
}
|
||||
a.AIClient.Close()
|
||||
}
|
||||
|
||||
@@ -58,8 +58,12 @@ 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)))
|
||||
// Print the AI provider used for this analysis (if explain was enabled).
|
||||
if a.Explain {
|
||||
output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString(a.AnalysisAIProvider)))
|
||||
} else {
|
||||
output.WriteString(fmt.Sprintf("AI Provider: %s\n", color.YellowString("AI not used; --explain not set")))
|
||||
}
|
||||
|
||||
if len(a.Errors) != 0 {
|
||||
output.WriteString("\n")
|
||||
|
||||
@@ -50,6 +50,9 @@ var additionalAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"PodDisruptionBudget": PdbAnalyzer{},
|
||||
"NetworkPolicy": NetworkPolicyAnalyzer{},
|
||||
"Log": LogAnalyzer{},
|
||||
"GatewayClass": GatewayClassAnalyzer{},
|
||||
"Gateway": GatewayAnalyzer{},
|
||||
"HTTPRoute": HTTPRouteAnalyzer{},
|
||||
}
|
||||
|
||||
func ListFilters() ([]string, []string, []string) {
|
||||
|
||||
108
pkg/analyzer/gateway.go
Normal file
108
pkg/analyzer/gateway.go
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
type GatewayAnalyzer struct{}
|
||||
|
||||
// Gateway analyser will analyse all different Kinds and search for missing object dependencies
|
||||
func (GatewayAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "Gateway"
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
gtwList := >wapi.GatewayList{}
|
||||
gc := >wapi.GatewayClass{}
|
||||
client := a.Client.CtrlClient
|
||||
gtwapi.AddToScheme(client.Scheme())
|
||||
if err := client.List(a.Context, gtwList, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
// Find all unhealthy gateway Classes
|
||||
|
||||
for _, gtw := range gtwList.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
gtwName := gtw.GetName()
|
||||
gtwNamespace := gtw.GetNamespace()
|
||||
// Check if gatewayclass exists
|
||||
err := client.Get(a.Context, ctrl.ObjectKey{Namespace: gtwNamespace, Name: string(gtw.Spec.GatewayClassName)}, gc, &ctrl.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"Gateway uses the GatewayClass %s which does not exist.",
|
||||
gtw.Spec.GatewayClassName,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: string(gtw.Spec.GatewayClassName),
|
||||
Masked: util.MaskString(string(gtw.Spec.GatewayClassName)),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Check only the current conditions
|
||||
// TODO: maybe check other statuses Listeners, addresses?
|
||||
if gtw.Status.Conditions[0].Status != metav1.ConditionTrue {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Gateway '%s/%s' is not accepted. Message: '%s'.",
|
||||
gtwNamespace,
|
||||
gtwName,
|
||||
gtw.Status.Conditions[0].Message,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: gtwNamespace,
|
||||
Masked: util.MaskString(gtwNamespace),
|
||||
},
|
||||
{
|
||||
Unmasked: gtwName,
|
||||
Masked: util.MaskString(gtwName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", gtwNamespace, gtwName)] = common.PreAnalysis{
|
||||
Gateway: gtw,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, gtwName, gtwNamespace).Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
}
|
||||
161
pkg/analyzer/gateway_test.go
Normal file
161
pkg/analyzer/gateway_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func BuildGatewayClass(name string) gtwapi.GatewayClass {
|
||||
GatewayClass := gtwapi.GatewayClass{}
|
||||
GatewayClass.Name = name
|
||||
// Namespace is not needed outside of this test, GatewayClass is cluster-scoped
|
||||
GatewayClass.Namespace = "default"
|
||||
GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
|
||||
|
||||
return GatewayClass
|
||||
}
|
||||
|
||||
func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus) gtwapi.Gateway {
|
||||
Gateway := gtwapi.Gateway{}
|
||||
Gateway.Name = "foobar"
|
||||
Gateway.Namespace = "default"
|
||||
Gateway.Spec.GatewayClassName = className
|
||||
Gateway.Spec.Listeners = []gtwapi.Listener{
|
||||
{
|
||||
Name: "proxy",
|
||||
Port: 80,
|
||||
Protocol: gtwapi.HTTPProtocolType,
|
||||
},
|
||||
}
|
||||
Condition := metav1.Condition{
|
||||
Type: "Accepted",
|
||||
Status: status,
|
||||
Message: "An expected message",
|
||||
Reason: "Test",
|
||||
}
|
||||
Gateway.Status.Conditions = []metav1.Condition{Condition}
|
||||
|
||||
return Gateway
|
||||
}
|
||||
|
||||
func TestGatewayAnalyzer(t *testing.T) {
|
||||
ClassName := gtwapi.ObjectName("exists")
|
||||
AcceptedStatus := metav1.ConditionTrue
|
||||
GatewayClass := BuildGatewayClass(string(ClassName))
|
||||
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&Gateway,
|
||||
&GatewayClass,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := GatewayAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
|
||||
}
|
||||
|
||||
func TestMissingClassGatewayAnalyzer(t *testing.T) {
|
||||
ClassName := gtwapi.ObjectName("non-existed")
|
||||
AcceptedStatus := metav1.ConditionTrue
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&Gateway,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := GatewayAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestStatusGatewayAnalyzer(t *testing.T) {
|
||||
ClassName := gtwapi.ObjectName("exists")
|
||||
AcceptedStatus := metav1.ConditionUnknown
|
||||
GatewayClass := BuildGatewayClass(string(ClassName))
|
||||
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&Gateway,
|
||||
&GatewayClass,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := GatewayAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var errorFound bool
|
||||
want := "Gateway 'default/foobar' is not accepted. Message: 'An expected message'."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%v> , not found in Gateway's analysis results", want)
|
||||
}
|
||||
}
|
||||
84
pkg/analyzer/gatewayclass.go
Normal file
84
pkg/analyzer/gatewayclass.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
type GatewayClassAnalyzer struct{}
|
||||
|
||||
// Gateway analyser will analyse all different Kinds and search for missing object dependencies
|
||||
func (GatewayClassAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "GatewayClass"
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
gcList := >wapi.GatewayClassList{}
|
||||
client := a.Client.CtrlClient
|
||||
gtwapi.AddToScheme(client.Scheme())
|
||||
if err := client.List(a.Context, gcList, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
// Find all unhealthy gateway Classes
|
||||
|
||||
for _, gc := range gcList.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
gcName := gc.GetName()
|
||||
// Check only the current condition
|
||||
if gc.Status.Conditions[0].Status != metav1.ConditionTrue {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"GatewayClass '%s' with a controller name '%s' is not accepted. Message: '%s'.",
|
||||
gcName,
|
||||
gc.Spec.ControllerName,
|
||||
gc.Status.Conditions[0].Message,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: gcName,
|
||||
Masked: util.MaskString(gcName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[gcName] = common.PreAnalysis{
|
||||
GatewayClass: gc,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, gcName, "").Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
}
|
||||
51
pkg/analyzer/gatewayclass_test.go
Normal file
51
pkg/analyzer/gatewayclass_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
// Testing with the fake dynamic client if GatewayClasses have an accepted status
|
||||
func TestGatewayClassAnalyzer(t *testing.T) {
|
||||
GatewayClass := >wapi.GatewayClass{}
|
||||
GatewayClass.Name = "foobar"
|
||||
GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
|
||||
// Initialize Conditions slice before setting properties
|
||||
BadCondition := metav1.Condition{
|
||||
Type: "Accepted",
|
||||
Status: "Uknown",
|
||||
Message: "Waiting for controller",
|
||||
Reason: "Pending",
|
||||
}
|
||||
GatewayClass.Status.Conditions = []metav1.Condition{BadCondition}
|
||||
// Create a GatewayClassAnalyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass).Build()
|
||||
|
||||
analyzerInstance := GatewayClassAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
228
pkg/analyzer/httroute.go
Normal file
228
pkg/analyzer/httroute.go
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
type HTTPRouteAnalyzer struct{}
|
||||
|
||||
// Gateway analyser will analyse all different Kinds and search for missing object dependencies
|
||||
func (HTTPRouteAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "HTTPRoute"
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
routeList := >wapi.HTTPRouteList{}
|
||||
gtw := >wapi.Gateway{}
|
||||
service := &corev1.Service{}
|
||||
client := a.Client.CtrlClient
|
||||
gtwapi.AddToScheme(client.Scheme())
|
||||
if err := client.List(a.Context, routeList, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
// Find all unhealthy gateway Classes
|
||||
|
||||
for _, route := range routeList.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check if Gateways exists in the same or designated namespace
|
||||
// TODO: when meshes and ClusterIp options are adopted we can add more checks
|
||||
// e.g Service Port matching
|
||||
for _, gtwref := range route.Spec.ParentRefs {
|
||||
namespace := route.Namespace
|
||||
if gtwref.Namespace != nil {
|
||||
namespace = string(*gtwref.Namespace)
|
||||
}
|
||||
err := client.Get(a.Context, ctrl.ObjectKey{Namespace: namespace, Name: string(gtwref.Name)}, gtw, &ctrl.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"HTTPRoute uses the Gateway '%s/%s' which does not exist in the same namespace.",
|
||||
namespace,
|
||||
gtwref.Name,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: gtw.Namespace,
|
||||
Masked: util.MaskString(gtw.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: gtw.Name,
|
||||
Masked: util.MaskString(gtw.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
// Check if the aforementioned Gateway allows the HTTPRoutes from the route's namespace
|
||||
for _, listener := range gtw.Spec.Listeners {
|
||||
if listener.AllowedRoutes.Namespaces != nil {
|
||||
switch allow := listener.AllowedRoutes.Namespaces.From; {
|
||||
case *allow == gtwapi.NamespacesFromSame:
|
||||
// check if Gateway is in the same namespace
|
||||
if route.Namespace != gtw.Namespace {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("HTTPRoute '%s/%s' is deployed in a different namespace from Gateway '%s/%s' which only allows HTTPRoutes from its namespace.",
|
||||
route.Namespace,
|
||||
route.Name,
|
||||
gtw.Namespace,
|
||||
gtw.Name,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: route.Namespace,
|
||||
Masked: util.MaskString(route.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: route.Name,
|
||||
Masked: util.MaskString(route.Name),
|
||||
},
|
||||
{
|
||||
Unmasked: gtw.Namespace,
|
||||
Masked: util.MaskString(gtw.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: gtw.Name,
|
||||
Masked: util.MaskString(gtw.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
case *allow == gtwapi.NamespacesFromSelector:
|
||||
// check if our route include the same selector Label
|
||||
if !util.LabelsIncludeAny(listener.AllowedRoutes.Namespaces.Selector.MatchLabels, route.Labels) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"HTTPRoute '%s/%s' can't be attached on Gateway '%s/%s', selector labels do not match HTTProute's labels.",
|
||||
route.Namespace,
|
||||
route.Name,
|
||||
gtw.Namespace,
|
||||
gtw.Name,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: route.Namespace,
|
||||
Masked: util.MaskString(route.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: route.Name,
|
||||
Masked: util.MaskString(route.Name),
|
||||
},
|
||||
{
|
||||
Unmasked: gtw.Namespace,
|
||||
Masked: util.MaskString(gtw.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: gtw.Name,
|
||||
Masked: util.MaskString(gtw.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Check if the Backends are valid services and ports are matching with services Ports
|
||||
for _, rule := range route.Spec.Rules {
|
||||
for _, backend := range rule.BackendRefs {
|
||||
err := client.Get(a.Context, ctrl.ObjectKey{Namespace: route.Namespace, Name: string(backend.Name)}, service, &ctrl.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"HTTPRoute uses the Service '%s/%s' which does not exist.",
|
||||
route.Namespace,
|
||||
backend.Name,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: service.Namespace,
|
||||
Masked: util.MaskString(service.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: service.Name,
|
||||
Masked: util.MaskString(service.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
portMatch := false
|
||||
for _, svcPort := range service.Spec.Ports {
|
||||
if int32(*backend.Port) == svcPort.Port {
|
||||
portMatch = true
|
||||
}
|
||||
}
|
||||
if !portMatch {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf(
|
||||
"HTTPRoute's backend service '%s' is using port '%d' but the corresponding K8s service '%s/%s' isn't configured with the same port.",
|
||||
backend.Name,
|
||||
int32(*backend.Port),
|
||||
service.Namespace,
|
||||
service.Name,
|
||||
),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: string(backend.Name),
|
||||
Masked: util.MaskString(string(backend.Name)),
|
||||
},
|
||||
{
|
||||
Unmasked: service.Name,
|
||||
Masked: util.MaskString(service.Name),
|
||||
},
|
||||
{
|
||||
Unmasked: service.Namespace,
|
||||
Masked: service.Namespace,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", route.Namespace, route.Name)] = common.PreAnalysis{
|
||||
HTTPRoute: route,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, route.Name, route.Namespace).Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
|
||||
}
|
||||
374
pkg/analyzer/httroute_test.go
Normal file
374
pkg/analyzer/httroute_test.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
func BuildRouteGateway(namespace, name, fromNamespaceref string) gtwapi.Gateway {
|
||||
routeNamespace := >wapi.RouteNamespaces{}
|
||||
switch fromNamespaceref {
|
||||
case "Same":
|
||||
fromSame := gtwapi.NamespacesFromSame
|
||||
routeNamespace.From = &fromSame
|
||||
case "Selector":
|
||||
fromSelector := gtwapi.NamespacesFromSelector
|
||||
routeNamespace.From = &fromSelector
|
||||
routeNamespace.Selector = &metav1.LabelSelector{}
|
||||
routeNamespace.Selector.MatchLabels = map[string]string{"foo": "bar"}
|
||||
|
||||
default:
|
||||
fromAll := gtwapi.NamespacesFromAll
|
||||
routeNamespace.From = &fromAll
|
||||
}
|
||||
Gateway := gtwapi.Gateway{}
|
||||
Gateway.Name = name
|
||||
Gateway.Namespace = namespace
|
||||
Gateway.Spec.GatewayClassName = "fooclassName"
|
||||
Gateway.Spec.Listeners = []gtwapi.Listener{
|
||||
{
|
||||
Name: "proxy",
|
||||
Port: 80,
|
||||
Protocol: gtwapi.HTTPProtocolType,
|
||||
AllowedRoutes: >wapi.AllowedRoutes{
|
||||
Namespaces: routeNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
Condition := metav1.Condition{
|
||||
Type: "Accepted",
|
||||
Status: "True",
|
||||
Message: "An expected message",
|
||||
Reason: "Test",
|
||||
}
|
||||
Gateway.Status.Conditions = []metav1.Condition{Condition}
|
||||
|
||||
return Gateway
|
||||
}
|
||||
|
||||
func BuildHTTPRoute(backendName, gtwName gtwapi.ObjectName, gtwNamespace gtwapi.Namespace, svcPort *gtwapi.PortNumber, namespace string) gtwapi.HTTPRoute {
|
||||
HTTPRoute := gtwapi.HTTPRoute{}
|
||||
HTTPRoute.Name = "foohttproute"
|
||||
HTTPRoute.Namespace = namespace
|
||||
HTTPRoute.Spec.ParentRefs = []gtwapi.ParentReference{
|
||||
{
|
||||
Name: gtwName,
|
||||
Namespace: >wNamespace,
|
||||
},
|
||||
}
|
||||
HTTPRoute.Spec.Rules = []gtwapi.HTTPRouteRule{
|
||||
{
|
||||
BackendRefs: []gtwapi.HTTPBackendRef{
|
||||
{
|
||||
BackendRef: gtwapi.BackendRef{
|
||||
BackendObjectReference: gtwapi.BackendObjectReference{
|
||||
Name: backendName,
|
||||
Port: svcPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return HTTPRoute
|
||||
}
|
||||
|
||||
/*
|
||||
Testing different cases
|
||||
|
||||
1. Gateway doesn't exist or at least doesn't exist in the same namespace
|
||||
2. Gateway exists in different namespace, is configured in httproute's spec
|
||||
and Gateway's configuration is allowing only from its same namespace
|
||||
3. Gateway exists in the same namespace but has selectors different from route's labels
|
||||
4. BackendRef is pointing to a non existent Service
|
||||
5. BackendRef's port and Service Port are different
|
||||
*/
|
||||
func TestGWMissiningHTTRouteAnalyzer(t *testing.T) {
|
||||
backendName := gtwapi.ObjectName("foobackend")
|
||||
gtwName := gtwapi.ObjectName("non-existent")
|
||||
gtwNamespace := gtwapi.Namespace("non-existent")
|
||||
svcPort := gtwapi.PortNumber(1027)
|
||||
httpRouteNamespace := "default"
|
||||
|
||||
HTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&HTTPRoute,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := HTTPRouteAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
want := "HTTPRoute uses the Gateway 'non-existent/non-existent' which does not exist in the same namespace."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%s> , not found in HTTPRoute's analysis results", want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGWConfigSameHTTRouteAnalyzer(t *testing.T) {
|
||||
backendName := gtwapi.ObjectName("foobackend")
|
||||
gtwName := gtwapi.ObjectName("gatewayname")
|
||||
gtwNamespace := gtwapi.Namespace("differentnamespace")
|
||||
svcPort := gtwapi.PortNumber(1027)
|
||||
httpRouteNamespace := "default"
|
||||
|
||||
HTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)
|
||||
|
||||
Gateway := BuildRouteGateway("differentnamespace", "gatewayname", "Same")
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&HTTPRoute,
|
||||
&Gateway,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := HTTPRouteAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
want := "HTTPRoute 'default/foohttproute' is deployed in a different namespace from Gateway 'differentnamespace/gatewayname' which only allows HTTPRoutes from its namespace."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%s> , not found in HTTPRoute's analysis results", want)
|
||||
}
|
||||
}
|
||||
func TestGWConfigSelectorHTTRouteAnalyzer(t *testing.T) {
|
||||
backendName := gtwapi.ObjectName("foobackend")
|
||||
gtwName := gtwapi.ObjectName("gatewayname")
|
||||
gtwNamespace := gtwapi.Namespace("default")
|
||||
svcPort := gtwapi.PortNumber(1027)
|
||||
httpRouteNamespace := "default"
|
||||
|
||||
HTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)
|
||||
|
||||
Gateway := BuildRouteGateway("default", "gatewayname", "Selector")
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&HTTPRoute,
|
||||
&Gateway,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := HTTPRouteAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
want := "HTTPRoute 'default/foohttproute' can't be attached on Gateway 'default/gatewayname', selector labels do not match HTTProute's labels."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%s> , not found in HTTPRoute's analysis results", want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSvcMissingHTTRouteAnalyzer(t *testing.T) {
|
||||
backendName := gtwapi.ObjectName("foobackend")
|
||||
gtwName := gtwapi.ObjectName("gatewayname")
|
||||
gtwNamespace := gtwapi.Namespace("default")
|
||||
svcPort := gtwapi.PortNumber(1027)
|
||||
httpRouteNamespace := "default"
|
||||
|
||||
HTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)
|
||||
|
||||
Gateway := BuildRouteGateway("default", "gatewayname", "Same")
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&HTTPRoute,
|
||||
&Gateway,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := HTTPRouteAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
want := "HTTPRoute uses the Service 'default/foobackend' which does not exist."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%s> , not found in HTTPRoute's analysis results", want)
|
||||
}
|
||||
}
|
||||
func TestSvcDifferentPortHTTRouteAnalyzer(t *testing.T) {
|
||||
//Add a Service Object
|
||||
Service := corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foobackend",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Protocol: "TCP",
|
||||
Port: 80,
|
||||
TargetPort: intstr.FromInt(8080),
|
||||
},
|
||||
},
|
||||
Type: corev1.ServiceTypeClusterIP,
|
||||
},
|
||||
}
|
||||
backendName := gtwapi.ObjectName("foobackend")
|
||||
gtwName := gtwapi.ObjectName("gatewayname")
|
||||
gtwNamespace := gtwapi.Namespace("default")
|
||||
// different port
|
||||
svcPort := gtwapi.PortNumber(1027)
|
||||
httpRouteNamespace := "default"
|
||||
|
||||
HTTPRoute := BuildHTTPRoute(backendName, gtwName, gtwNamespace, &svcPort, httpRouteNamespace)
|
||||
|
||||
Gateway := BuildRouteGateway("default", "gatewayname", "Same")
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
gtwapi.Install(scheme)
|
||||
apiextensionsv1.AddToScheme(scheme)
|
||||
objects := []runtime.Object{
|
||||
&HTTPRoute,
|
||||
&Gateway,
|
||||
&Service,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := HTTPRouteAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
want := "HTTPRoute's backend service 'foobackend' is using port '1027' but the corresponding K8s service 'default/foobackend' isn't configured with the same port."
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !errorFound {
|
||||
t.Errorf("Expected message, <%s> , not found in HTTPRoute's analysis results", want)
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
} else {
|
||||
rawlogs := string(podLogs)
|
||||
if errorPattern.MatchString(rawlogs) {
|
||||
if errorPattern.MatchString(strings.ToLower(rawlogs)) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: printErrorLines(pod.Name, pod.Namespace, rawlogs, errorPattern),
|
||||
Sensitive: []common.Sensitive{
|
||||
@@ -108,7 +108,7 @@ func printErrorLines(podName, namespace, logs string, errorPattern *regexp.Regex
|
||||
|
||||
// Check each line for errors and print the lines containing errors
|
||||
for _, line := range logLines {
|
||||
if errorPattern.MatchString(line) {
|
||||
if errorPattern.MatchString(strings.ToLower(line)) {
|
||||
return line
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled
|
||||
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("the last termination reason is %s container=%s pod=%s", containerStatus.LastTerminationState.Terminated.Reason, containerStatus.Name, pod.Name),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// when pod is Running but its ReadinessProbe fails
|
||||
if !containerStatus.Ready && pod.Status.Phase == "Running" {
|
||||
|
||||
4
pkg/cache/cache.go
vendored
4
pkg/cache/cache.go
vendored
@@ -93,9 +93,9 @@ func GetCacheConfiguration() (ICache, error) {
|
||||
cache = &FileBasedCache{}
|
||||
}
|
||||
|
||||
cache.Configure(cacheInfo)
|
||||
err_config := cache.Configure(cacheInfo)
|
||||
|
||||
return cache, nil
|
||||
return cache, err_config
|
||||
}
|
||||
|
||||
func AddRemoteCache(cacheInfo CacheProvider) error {
|
||||
|
||||
4
pkg/cache/s3_based.go
vendored
4
pkg/cache/s3_based.go
vendored
@@ -90,9 +90,9 @@ func (s *S3Cache) Load(key string) (string, error) {
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(result.Body)
|
||||
_, err_read := buf.ReadFrom(result.Body)
|
||||
result.Body.Close()
|
||||
return buf.String(), nil
|
||||
return buf.String(), err_read
|
||||
}
|
||||
|
||||
func (s *S3Cache) List() ([]CacheObjectDetails, error) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
type IAnalyzer interface {
|
||||
@@ -57,6 +58,9 @@ type PreAnalysis struct {
|
||||
Node v1.Node
|
||||
ValidatingWebhook regv1.ValidatingWebhookConfiguration
|
||||
MutatingWebhook regv1.MutatingWebhookConfiguration
|
||||
GatewayClass gtwapi.GatewayClass
|
||||
Gateway gtwapi.Gateway
|
||||
HTTPRoute gtwapi.HTTPRoute
|
||||
// Integrations
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
TrivyConfigAuditReport trivy.ConfigAuditReport
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/prometheus"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/spf13/viper"
|
||||
@@ -44,7 +45,8 @@ type Integration struct {
|
||||
}
|
||||
|
||||
var integrations = map[string]IIntegration{
|
||||
"trivy": trivy.NewTrivy(),
|
||||
"trivy": trivy.NewTrivy(),
|
||||
"prometheus": prometheus.NewPrometheus(),
|
||||
}
|
||||
|
||||
func NewIntegration() *Integration {
|
||||
|
||||
290
pkg/integration/prometheus/config_analyzer.go
Normal file
290
pkg/integration/prometheus/config_analyzer.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
promconfig "github.com/prometheus/prometheus/config"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
prometheusContainerName = "prometheus"
|
||||
configReloaderContainerName = "config-reloader"
|
||||
prometheusConfigFlag = "--config.file="
|
||||
configReloaderConfigFlag = "--config-file="
|
||||
)
|
||||
|
||||
var prometheusPodLabels = map[string]string{
|
||||
"app": "prometheus",
|
||||
"app.kubernetes.io/name": "prometheus",
|
||||
}
|
||||
|
||||
type ConfigAnalyzer struct {
|
||||
}
|
||||
|
||||
// podConfig groups a specific pod with the Prometheus configuration and any
|
||||
// other state used for informing the common.Result.
|
||||
type podConfig struct {
|
||||
b []byte
|
||||
pod *corev1.Pod
|
||||
}
|
||||
|
||||
func (c *ConfigAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
ctx := a.Context
|
||||
client := a.Client.GetClient()
|
||||
namespace := a.Namespace
|
||||
kind := ConfigValidate
|
||||
|
||||
podConfigs, err := findPrometheusPodConfigs(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
for _, pc := range podConfigs {
|
||||
var failures []common.Failure
|
||||
pod := pc.pod
|
||||
|
||||
// Check upstream validation.
|
||||
// The Prometheus configuration structs do not generally have validation
|
||||
// methods and embed their validation logic in the UnmarshalYAML methods.
|
||||
config, err := unmarshalPromConfigBytes(pc.b)
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("error validating Prometheus YAML configuration: %s", err),
|
||||
})
|
||||
}
|
||||
_, err = yaml.Marshal(config)
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("error validating Prometheus struct configuration: %s", err),
|
||||
})
|
||||
}
|
||||
|
||||
// Check for empty scrape config.
|
||||
if len(config.ScrapeConfigs) == 0 {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: "no scrape configurations. Prometheus will not scrape any metrics.",
|
||||
})
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
|
||||
Pod: *pod,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
|
||||
func configKey(namespace string, volume *corev1.Volume) (string, error) {
|
||||
if volume.ConfigMap != nil {
|
||||
return fmt.Sprintf("configmap/%s/%s", namespace, volume.ConfigMap.Name), nil
|
||||
} else if volume.Secret != nil {
|
||||
return fmt.Sprintf("secret/%s/%s", namespace, volume.Secret.SecretName), nil
|
||||
} else {
|
||||
return "", errors.New("volume format must be ConfigMap or Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func findPrometheusPodConfigs(ctx context.Context, client kubernetes.Interface, namespace string) ([]podConfig, error) {
|
||||
var configs []podConfig
|
||||
pods, err := findPrometheusPods(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var configCache = make(map[string]bool)
|
||||
|
||||
for _, pod := range pods {
|
||||
// Extract volume of Promethues config.
|
||||
volume, key, err := findPrometheusConfigVolumeAndKey(ctx, client, &pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// See if we processed it already; if so, don't process again.
|
||||
ck, err := configKey(pod.Namespace, volume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ok := configCache[ck]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
configCache[ck] = true
|
||||
|
||||
// Extract Prometheus config bytes from volume.
|
||||
b, err := extractPrometheusConfigFromVolume(ctx, client, volume, pod.Namespace, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configs = append(configs, podConfig{
|
||||
pod: &pod,
|
||||
b: b,
|
||||
})
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func findPrometheusPods(ctx context.Context, client kubernetes.Interface, namespace string) ([]corev1.Pod, error) {
|
||||
var proms []corev1.Pod
|
||||
for k, v := range prometheusPodLabels {
|
||||
pods, err := util.GetPodListByLabels(client, namespace, map[string]string{
|
||||
k: v,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proms = append(proms, pods.Items...)
|
||||
}
|
||||
|
||||
// If we still haven't found any Prometheus pods, make a last-ditch effort to
|
||||
// scrape the namespace for "prometheus" containers.
|
||||
if len(proms) == 0 {
|
||||
pods, err := client.CoreV1().Pods(namespace).List(ctx, v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pod := range pods.Items {
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.Name == prometheusContainerName {
|
||||
proms = append(proms, pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return proms, nil
|
||||
}
|
||||
|
||||
func findPrometheusConfigPath(ctx context.Context, client kubernetes.Interface, pod *corev1.Pod) (string, error) {
|
||||
var path string
|
||||
var err error
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for _, arg := range container.Args {
|
||||
// Prefer the config-reloader container config file as it normally
|
||||
// references the ConfigMap or Secret volume mount.
|
||||
// Fallback to the prometheus container if that's not found.
|
||||
if strings.HasPrefix(arg, prometheusConfigFlag) {
|
||||
path = strings.TrimLeft(arg, prometheusConfigFlag)
|
||||
}
|
||||
if strings.HasPrefix(arg, configReloaderConfigFlag) {
|
||||
path = strings.TrimLeft(arg, configReloaderConfigFlag)
|
||||
}
|
||||
}
|
||||
if container.Name == configReloaderContainerName {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
err = fmt.Errorf("prometheus config path not found in pod: %s", pod.Name)
|
||||
}
|
||||
return path, err
|
||||
}
|
||||
|
||||
func findPrometheusConfigVolumeAndKey(ctx context.Context, client kubernetes.Interface, pod *corev1.Pod) (*corev1.Volume, string, error) {
|
||||
path, err := findPrometheusConfigPath(ctx, client, pod)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Find the volumeMount the config path is pointing to.
|
||||
var volumeName = ""
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for _, vm := range container.VolumeMounts {
|
||||
if strings.HasPrefix(path, vm.MountPath) {
|
||||
volumeName = vm.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the actual Volume from the name.
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName {
|
||||
return &volume, filepath.Base(path), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", errors.New("volume for Prometheus config not found")
|
||||
}
|
||||
|
||||
func extractPrometheusConfigFromVolume(ctx context.Context, client kubernetes.Interface, volume *corev1.Volume, namespace, key string) ([]byte, error) {
|
||||
var b []byte
|
||||
var ok bool
|
||||
// Check for Secret volume.
|
||||
if vs := volume.Secret; vs != nil {
|
||||
s, err := client.CoreV1().Secrets(namespace).Get(ctx, vs.SecretName, v1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, ok = s.Data[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to find file key in secret: %s", key)
|
||||
}
|
||||
}
|
||||
// Check for ConfigMap volume.
|
||||
if vcm := volume.ConfigMap; vcm != nil {
|
||||
cm, err := client.CoreV1().ConfigMaps(namespace).Get(ctx, vcm.Name, v1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s, ok := cm.Data[key]
|
||||
b = []byte(s)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to find file key in configmap: %s", key)
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func unmarshalPromConfigBytes(b []byte) (*promconfig.Config, error) {
|
||||
var config promconfig.Config
|
||||
// Unmarshal the data into a Prometheus config.
|
||||
if err := yaml.Unmarshal(b, &config); err == nil {
|
||||
return &config, nil
|
||||
// If there were errors, try gunziping the data.
|
||||
} else if content := http.DetectContentType(b); content == "application/x-gzip" {
|
||||
r, err := gzip.NewReader(bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return &config, err
|
||||
}
|
||||
gunzipBytes, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return &config, err
|
||||
}
|
||||
err = yaml.Unmarshal(gunzipBytes, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
} else {
|
||||
return &config, err
|
||||
}
|
||||
}
|
||||
105
pkg/integration/prometheus/prometheus.go
Normal file
105
pkg/integration/prometheus/prometheus.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigValidate = "PrometheusConfigValidate"
|
||||
ConfigRelabel = "PrometheusConfigRelabelReport"
|
||||
)
|
||||
|
||||
type Prometheus struct {
|
||||
}
|
||||
|
||||
func NewPrometheus() *Prometheus {
|
||||
return &Prometheus{}
|
||||
}
|
||||
|
||||
func (p *Prometheus) Deploy(namespace string) error {
|
||||
// no-op
|
||||
color.Green("Activating prometheus integration...")
|
||||
// TODO(pintohutch): add timeout or inherit an upstream context
|
||||
// for better signal management.
|
||||
ctx := context.Background()
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
kubeconfig := viper.GetString("kubeconfig")
|
||||
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
|
||||
if err != nil {
|
||||
color.Red("Error initialising kubernetes client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// We just care about existing deployments.
|
||||
// Try and find Prometheus configurations in the cluster using the provided namespace.
|
||||
//
|
||||
// Note: We could cache this state and inject it into the various analyzers
|
||||
// to save additional parsing later.
|
||||
// However, the state of the cluster can change from activation to analysis,
|
||||
// so we would want to run this again on each analyze call anyway.
|
||||
//
|
||||
// One consequence of this is one can run `activate` in one namespace
|
||||
// and run `analyze` in another, without issues, as long as Prometheus
|
||||
// is found in both.
|
||||
// We accept this as a trade-off for the time-being to avoid having the tool
|
||||
// manage Prometheus on the behalf of users.
|
||||
podConfigs, err := findPrometheusPodConfigs(ctx, client.GetClient(), namespace)
|
||||
if err != nil {
|
||||
color.Red("Error discovering Prometheus worklads: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(podConfigs) == 0 {
|
||||
color.Yellow(fmt.Sprintf(`Prometheus installation not found in namespace: %s.
|
||||
Please ensure Prometheus is deployed to analyze.`, namespace))
|
||||
return errors.New("no prometheus installation found")
|
||||
}
|
||||
// Prime state of the analyzer so
|
||||
color.Green("Found existing installation")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Prometheus) UnDeploy(_ string) error {
|
||||
// no-op
|
||||
// We just care about existing deployments.
|
||||
color.Yellow("Integration will leave Prometheus resources deployed. This is an effective no-op in the cluster.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Prometheus) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
|
||||
(*mergedMap)[ConfigValidate] = &ConfigAnalyzer{}
|
||||
(*mergedMap)[ConfigRelabel] = &RelabelAnalyzer{}
|
||||
}
|
||||
|
||||
func (p *Prometheus) GetAnalyzerName() []string {
|
||||
return []string{ConfigValidate, ConfigRelabel}
|
||||
}
|
||||
|
||||
func (p *Prometheus) GetNamespace() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p *Prometheus) OwnsAnalyzer(analyzer string) bool {
|
||||
return (analyzer == ConfigValidate) || (analyzer == ConfigRelabel)
|
||||
}
|
||||
|
||||
func (t *Prometheus) IsActivate() bool {
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
for _, filter := range t.GetAnalyzerName() {
|
||||
for _, af := range activeFilters {
|
||||
if af == filter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
85
pkg/integration/prometheus/relabel_analyzer.go
Normal file
85
pkg/integration/prometheus/relabel_analyzer.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
discoverykube "github.com/prometheus/prometheus/discovery/kubernetes"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type RelabelAnalyzer struct {
|
||||
}
|
||||
|
||||
func (r *RelabelAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
ctx := a.Context
|
||||
client := a.Client.GetClient()
|
||||
namespace := a.Namespace
|
||||
kind := ConfigRelabel
|
||||
|
||||
podConfigs, err := findPrometheusPodConfigs(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
for _, pc := range podConfigs {
|
||||
var failures []common.Failure
|
||||
pod := pc.pod
|
||||
|
||||
// Check upstream validation.
|
||||
// The Prometheus configuration structs do not generally have validation
|
||||
// methods and embed their validation logic in the UnmarshalYAML methods.
|
||||
config, _ := unmarshalPromConfigBytes(pc.b)
|
||||
// Limit output for brevity.
|
||||
limit := 6
|
||||
i := 0
|
||||
for _, sc := range config.ScrapeConfigs {
|
||||
if i == limit {
|
||||
break
|
||||
}
|
||||
if sc == nil {
|
||||
continue
|
||||
}
|
||||
brc, _ := yaml.Marshal(sc.RelabelConfigs)
|
||||
var bsd []byte
|
||||
for _, cfg := range sc.ServiceDiscoveryConfigs {
|
||||
ks, ok := cfg.(*discoverykube.SDConfig)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
bsd, _ = yaml.Marshal(ks)
|
||||
}
|
||||
// Don't bother with relabel analysis if the scrape config
|
||||
// or service discovery config are empty.
|
||||
if len(brc) == 0 || len(bsd) == 0 {
|
||||
continue
|
||||
}
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("job_name:\n%s\nrelabel_configs:\n%s\nkubernetes_sd_configs:\n%s\n", sc.JobName, string(brc), string(bsd)),
|
||||
})
|
||||
i++
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
|
||||
Pod: *pod,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
@@ -15,12 +15,12 @@ package trivy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type TrivyAnalyzer struct {
|
||||
@@ -32,18 +32,9 @@ func (TrivyAnalyzer) analyzeVulnerabilityReports(a common.Analyzer) ([]common.Re
|
||||
// Get all trivy VulnerabilityReports
|
||||
result := &v1alpha1.VulnerabilityReportList{}
|
||||
|
||||
config := a.Client.GetConfig()
|
||||
// Add group version to sceheme
|
||||
config.ContentConfig.GroupVersion = &v1alpha1.SchemeGroupVersion
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
config.APIPath = "/apis"
|
||||
|
||||
restClient, err := rest.UnversionedRESTClientFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = restClient.Get().Resource("vulnerabilityreports").Namespace(a.Namespace).Do(a.Context).Into(result)
|
||||
if err != nil {
|
||||
client := a.Client.CtrlClient
|
||||
v1alpha1.AddToScheme(client.Scheme())
|
||||
if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -93,18 +84,9 @@ func (t TrivyAnalyzer) analyzeConfigAuditReports(a common.Analyzer) ([]common.Re
|
||||
// Get all trivy ConfigAuditReports
|
||||
result := &v1alpha1.ConfigAuditReportList{}
|
||||
|
||||
config := a.Client.GetConfig()
|
||||
// Add group version to sceheme
|
||||
config.ContentConfig.GroupVersion = &v1alpha1.SchemeGroupVersion
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
config.APIPath = "/apis"
|
||||
|
||||
restClient, err := rest.UnversionedRESTClientFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = restClient.Get().Resource("configauditreports").Namespace(a.Namespace).Do(a.Context).Into(result)
|
||||
if err != nil {
|
||||
client := a.Client.CtrlClient
|
||||
v1alpha1.AddToScheme(client.Scheme())
|
||||
if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
@@ -28,18 +29,26 @@ import (
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
const (
|
||||
Repo = "https://aquasecurity.github.io/helm-charts/"
|
||||
Version = "0.13.0"
|
||||
ChartName = "trivy-operator"
|
||||
RepoShortName = "aqua"
|
||||
ReleaseName = "trivy-operator-k8sgpt"
|
||||
var (
|
||||
Repo = getEnv("TRIVY_REPO", "https://aquasecurity.github.io/helm-charts/")
|
||||
Version = getEnv("TRIVY_VERSION", "0.13.0")
|
||||
ChartName = getEnv("TRIVY_CHART_NAME", "trivy-operator")
|
||||
RepoShortName = getEnv("TRIVY_REPO_SHORT_NAME", "aqua")
|
||||
ReleaseName = getEnv("TRIVY_RELEASE_NAME", "trivy-operator-k8sgpt")
|
||||
)
|
||||
|
||||
type Trivy struct {
|
||||
helm helmclient.Client
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func NewTrivy() *Trivy {
|
||||
helmClient, err := helmclient.New(&helmclient.Options{})
|
||||
if err != nil {
|
||||
|
||||
@@ -14,12 +14,11 @@ limitations under the License.
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func (c *Client) GetConfig() *rest.Config {
|
||||
@@ -30,8 +29,8 @@ func (c *Client) GetClient() kubernetes.Interface {
|
||||
return c.Client
|
||||
}
|
||||
|
||||
func (c *Client) GetRestClient() rest.Interface {
|
||||
return c.RestClient
|
||||
func (c *Client) GetCtrlClient() ctrl.Client {
|
||||
return c.CtrlClient
|
||||
}
|
||||
|
||||
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
@@ -59,11 +58,8 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.APIPath = "/api"
|
||||
config.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
|
||||
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
restClient, err := rest.RESTClientFor(config)
|
||||
ctrlClient, err := ctrl.New(config, ctrl.Options{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,7 +71,7 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
|
||||
return &Client{
|
||||
Client: clientSet,
|
||||
RestClient: restClient,
|
||||
CtrlClient: ctrlClient,
|
||||
Config: config,
|
||||
ServerVersion: serverVersion,
|
||||
}, nil
|
||||
|
||||
@@ -6,11 +6,12 @@ import (
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Client kubernetes.Interface
|
||||
RestClient rest.Interface
|
||||
CtrlClient ctrl.Client
|
||||
Config *rest.Config
|
||||
ServerVersion *version.Info
|
||||
}
|
||||
|
||||
@@ -33,10 +33,14 @@ func (h *handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (
|
||||
i.Explain,
|
||||
int(i.MaxConcurrency),
|
||||
false, // Kubernetes Doc disabled in server mode
|
||||
false, // Interactive mode disabled in server mode
|
||||
)
|
||||
config.Context = ctx // Replace context for correct timeouts.
|
||||
if err != nil {
|
||||
return &schemav1.AnalyzeResponse{}, err
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
config.RunAnalysis()
|
||||
|
||||
if i.Explain {
|
||||
|
||||
@@ -42,6 +42,7 @@ type Config struct {
|
||||
Handler *handler
|
||||
Logger *zap.Logger
|
||||
metricsServer *http.Server
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
type Health struct {
|
||||
@@ -56,6 +57,10 @@ var health = Health{
|
||||
Failure: 0,
|
||||
}
|
||||
|
||||
func (s *Config) Shutdown() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
func (s *Config) Serve() error {
|
||||
|
||||
var lis net.Listener
|
||||
@@ -65,6 +70,7 @@ func (s *Config) Serve() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = lis
|
||||
s.Logger.Info(fmt.Sprintf("binding api to %s", s.Port))
|
||||
grpcServerUnaryInterceptor := grpc.UnaryInterceptor(logInterceptor(s.Logger))
|
||||
grpcServer := grpc.NewServer(grpcServerUnaryInterceptor)
|
||||
|
||||
42
pkg/server/server_test.go
Normal file
42
pkg/server/server_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestServerInit(t *testing.T) {
|
||||
logger, err := zap.NewDevelopment()
|
||||
if err != nil {
|
||||
color.Red("failed to create logger: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer logger.Sync()
|
||||
server_config := Config{
|
||||
Backend: "openai",
|
||||
Port: "0",
|
||||
MetricsPort: "0",
|
||||
Token: "none",
|
||||
Logger: logger,
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
err := server_config.Serve()
|
||||
if err != nil {
|
||||
assert.Fail(t, "serve: %s", err.Error())
|
||||
}
|
||||
server_config.Shutdown()
|
||||
if err != nil {
|
||||
assert.Fail(t, "shutdown: %s", err.Error())
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -154,7 +154,10 @@ func SliceDiff(source, dest []string) []string {
|
||||
func MaskString(input string) string {
|
||||
key := make([]byte, len(input))
|
||||
result := make([]rune, len(input))
|
||||
rand.Read(key)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := range result {
|
||||
result[i] = anonymizePattern[int(key[i])%len(anonymizePattern)]
|
||||
}
|
||||
@@ -219,3 +222,14 @@ func MapToString(m map[string]string) string {
|
||||
}
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
|
||||
func LabelsIncludeAny(predefinedSelector, Labels map[string]string) bool {
|
||||
// Check if any label in the predefinedSelector exists in Labels
|
||||
for key := range predefinedSelector {
|
||||
if _, exists := Labels[key]; exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user