mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 11:33:08 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad117a530f | ||
|
|
16d57e5a55 | ||
|
|
3547c4808a | ||
|
|
407c855e14 | ||
|
|
d43fd878ba | ||
|
|
a068310731 | ||
|
|
8949f5bac3 | ||
|
|
f1b7b37fb8 | ||
|
|
391a3cd5ad | ||
|
|
3c08118104 | ||
|
|
eb3b81f176 | ||
|
|
5176759bd0 | ||
|
|
4b13727ef5 | ||
|
|
f9edbf34f3 | ||
|
|
1a00aafbb2 | ||
|
|
b6dd2a1181 | ||
|
|
4366ad97b8 | ||
|
|
34b6de3404 | ||
|
|
de9ef85878 | ||
|
|
0b906511d5 | ||
|
|
4d76e9c5ae | ||
|
|
593139cffb | ||
|
|
3e9340925c | ||
|
|
f6ce47c3a9 | ||
|
|
02e754ed59 | ||
|
|
fef853966f | ||
|
|
dd20dbc982 | ||
|
|
dd66355797 | ||
|
|
314f25ac8b | ||
|
|
d4abb33b3c | ||
|
|
27ac60aed2 | ||
|
|
0c0216096e | ||
|
|
b35dbd9b09 | ||
|
|
a075792119 | ||
|
|
ce63821beb | ||
|
|
ab534d184f |
10
.github/workflows/build_container.yaml
vendored
10
.github/workflows/build_container.yaml
vendored
@@ -74,10 +74,10 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
outputs: type=docker,dest=/tmp/${{ env.IMAGE_NAME }}-image.tar
|
||||
|
||||
- name: Upload image as artifact
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
with:
|
||||
name: ${{ env.IMAGE_NAME }}-image.tar
|
||||
path: /tmp/${{ env.IMAGE_NAME }}-image.tar
|
||||
@@ -126,10 +126,10 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
||||
with:
|
||||
context: .
|
||||
file: ./container/Dockerfile
|
||||
|
||||
14
.github/workflows/release.yaml
vendored
14
.github/workflows/release.yaml
vendored
@@ -45,13 +45,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
- name: Download Syft
|
||||
uses: anchore/sbom-action/download-syft@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11
|
||||
uses: anchore/sbom-action/download-syft@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5 # v0.17.0
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6 # v5
|
||||
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6
|
||||
with:
|
||||
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||
distribution: goreleaser
|
||||
@@ -59,6 +59,8 @@ jobs:
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.K8SGPT_BOT_SECRET }}
|
||||
- name: Update new version in krew-index
|
||||
uses: rajatjindal/krew-release-bot@df3eb197549e3568be8b4767eec31c5e8e8e6ad8 # v0.0.46
|
||||
|
||||
build-container:
|
||||
if: needs.release-please.outputs.releases_created == 'true'
|
||||
@@ -80,7 +82,7 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3
|
||||
@@ -90,7 +92,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
||||
with:
|
||||
context: .
|
||||
file: ./container/Dockerfile
|
||||
@@ -104,7 +106,7 @@ jobs:
|
||||
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@7ccf588e3cf3cc2611714c2eeae48550fbc17552 # v0.15.11
|
||||
uses: anchore/sbom-action@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5 # v0.17.0
|
||||
with:
|
||||
image: ${{ env.IMAGE_TAG }}
|
||||
artifact-name: sbom-${{ env.IMAGE_NAME }}
|
||||
|
||||
2
.github/workflows/semantic_pr.yaml
vendored
2
.github/workflows/semantic_pr.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
pull-requests: read # Needed for reading prs
|
||||
steps:
|
||||
- name: Validate Pull Request
|
||||
uses: amannn/action-semantic-pull-request@cfb60706e18bc85e8aec535e3c577abe8f70378e # v5.5.2
|
||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
|
||||
110
.krew.yaml
Normal file
110
.krew.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
apiVersion: krew.googlecontainertools.github.com/v1alpha2
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: gpt
|
||||
spec:
|
||||
version: {{ .TagName }}
|
||||
homepage: https://github.com/k8sgpt-ai/k8sgpt
|
||||
shortDescription: "Giving Kubernetes Superpowers to everyone"
|
||||
description: |
|
||||
A tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
|
||||
platforms:
|
||||
##########
|
||||
# Darwin #
|
||||
##########
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_x86_64.tar.gz" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: darwin
|
||||
arch: arm64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Darwin_arm64.tar.gz" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
|
||||
#########
|
||||
# Linux #
|
||||
#########
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: amd64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_x86_64.tar.gz" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: arm64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_arm64.tar.gz" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: linux
|
||||
arch: "386"
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Linux_i386.tar.gz" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
|
||||
###########
|
||||
# Windows #
|
||||
###########
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: amd64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_x86_64.zip" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: arm64
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_arm64.zip" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
- selector:
|
||||
matchLabels:
|
||||
os: windows
|
||||
arch: "386"
|
||||
{{addURIAndSha "https://github.com/k8sgpt-ai/k8sgpt/releases/download/{{ .TagName }}/k8sgpt_Windows_i386.zip" .TagName | indent 6 }}
|
||||
files:
|
||||
- from: "k8sgpt"
|
||||
to: "kubectl-gpt"
|
||||
- from: "LICENSE"
|
||||
to: "."
|
||||
bin: kubectl-gpt
|
||||
@@ -1 +1 @@
|
||||
{".":"0.3.37"}
|
||||
{".":"0.3.40"}
|
||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,5 +1,79 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.40](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.39...v0.3.40) (2024-08-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* custom analysis paralelism ([#1203](https://github.com/k8sgpt-ai/k8sgpt/issues/1203)) ([f1b7b37](https://github.com/k8sgpt-ai/k8sgpt/commit/f1b7b37fb83937d5fad90d7b6b52f4a38823da9e))
|
||||
* getting the error from status field for HPA analyzer ([#1164](https://github.com/k8sgpt-ai/k8sgpt/issues/1164)) ([a068310](https://github.com/k8sgpt-ai/k8sgpt/commit/a068310731d775beecede03a1709e541ffd68142))
|
||||
* initial custom analysis server mode ([#1205](https://github.com/k8sgpt-ai/k8sgpt/issues/1205)) ([16d57e5](https://github.com/k8sgpt-ai/k8sgpt/commit/16d57e5a55c2084bf1580377ae52e2961cc84922))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add default maxToken value of watsonxai backend ([#1209](https://github.com/k8sgpt-ai/k8sgpt/issues/1209)) ([d43fd87](https://github.com/k8sgpt-ai/k8sgpt/commit/d43fd878ba04fec8ac8afe4a1c15272b7f21c951))
|
||||
* auth update throw out exception ([#1193](https://github.com/k8sgpt-ai/k8sgpt/issues/1193)) ([391a3cd](https://github.com/k8sgpt-ai/k8sgpt/commit/391a3cd5adcbd90f37922332b4fad5ba5d813e5f))
|
||||
* **deps:** update module cloud.google.com/go/storage to v1.43.0 ([#1198](https://github.com/k8sgpt-ai/k8sgpt/issues/1198)) ([8949f5b](https://github.com/k8sgpt-ai/k8sgpt/commit/8949f5bac3c69130e30103511fdb5ece66e1619f))
|
||||
* **deps:** update module github.com/schollz/progressbar/v3 to v3.14.5 ([#1145](https://github.com/k8sgpt-ai/k8sgpt/issues/1145)) ([3547c48](https://github.com/k8sgpt-ai/k8sgpt/commit/3547c4808a846eb4392996afa20a84bdddf8e24f))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update anchore/sbom-action action to v0.17.0 ([#1197](https://github.com/k8sgpt-ai/k8sgpt/issues/1197)) ([407c855](https://github.com/k8sgpt-ai/k8sgpt/commit/407c855e147b73739e800310c926826344d36323))
|
||||
|
||||
## [0.3.39](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.38...v0.3.39) (2024-07-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add label selector ([#1201](https://github.com/k8sgpt-ai/k8sgpt/issues/1201)) ([eb3b81f](https://github.com/k8sgpt-ai/k8sgpt/commit/eb3b81f1767c589474864992ae78001ab1b376a1))
|
||||
* fix the custom-analysis printing ([#1195](https://github.com/k8sgpt-ai/k8sgpt/issues/1195)) ([b6dd2a1](https://github.com/k8sgpt-ai/k8sgpt/commit/b6dd2a1181b478a4fb8543ab7529ce595fa7d4a8))
|
||||
* initial kyverno support ([#1200](https://github.com/k8sgpt-ai/k8sgpt/issues/1200)) ([5176759](https://github.com/k8sgpt-ai/k8sgpt/commit/5176759bd0fad8671164f9e75b31dec19f02bd54))
|
||||
* skip k3s node type EtcdIsVoter ([#1167](https://github.com/k8sgpt-ai/k8sgpt/issues/1167)) ([4366ad9](https://github.com/k8sgpt-ai/k8sgpt/commit/4366ad97b80d2df0400e06e4b892fadab3939dc7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to 18e509b ([#1183](https://github.com/k8sgpt-ai/k8sgpt/issues/1183)) ([0b90651](https://github.com/k8sgpt-ai/k8sgpt/commit/0b906511d5a9837c9a67cf819754c610b1becc5c))
|
||||
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.4.0-20240715142657-3785f0a44aae.2 ([#1196](https://github.com/k8sgpt-ai/k8sgpt/issues/1196)) ([f9edbf3](https://github.com/k8sgpt-ai/k8sgpt/commit/f9edbf34f3eb3e90528d04b1c470fd6ef15293ec))
|
||||
* **deps:** update module github.com/ibm/watsonx-go to v1.0.1 ([#1187](https://github.com/k8sgpt-ai/k8sgpt/issues/1187)) ([34b6de3](https://github.com/k8sgpt-ai/k8sgpt/commit/34b6de34041ce253c1c680a7f5fe535b03a50da5))
|
||||
* **deps:** update module github.com/prometheus/prometheus to v0.53.1 ([#1035](https://github.com/k8sgpt-ai/k8sgpt/issues/1035)) ([de9ef85](https://github.com/k8sgpt-ai/k8sgpt/commit/de9ef8587822814542661e0039b47ef65d902abb))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** pin goreleaser/goreleaser-action action to 286f3b1 ([#1171](https://github.com/k8sgpt-ai/k8sgpt/issues/1171)) ([1a00aaf](https://github.com/k8sgpt-ai/k8sgpt/commit/1a00aafbb2f6f1482dfb3da7e96954b12ad5a4fd))
|
||||
* **deps:** update actions/setup-go digest to 0a12ed9 ([#1182](https://github.com/k8sgpt-ai/k8sgpt/issues/1182)) ([593139c](https://github.com/k8sgpt-ai/k8sgpt/commit/593139cffb1982fe45ccc9403acc893f51064271))
|
||||
* **deps:** update actions/upload-artifact digest to 0b2256b ([#1175](https://github.com/k8sgpt-ai/k8sgpt/issues/1175)) ([4b13727](https://github.com/k8sgpt-ai/k8sgpt/commit/4b13727ef579240adc2777d1126544fafb23b993))
|
||||
* **deps:** update anchore/sbom-action action to v0.16.1 ([#1179](https://github.com/k8sgpt-ai/k8sgpt/issues/1179)) ([3e93409](https://github.com/k8sgpt-ai/k8sgpt/commit/3e9340925c3d59861b1a95d5c1bc08c19ec26e4a))
|
||||
|
||||
## [0.3.38](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.37...v0.3.38) (2024-07-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add custom http headers to openai related api backends ([#1174](https://github.com/k8sgpt-ai/k8sgpt/issues/1174)) ([02e754e](https://github.com/k8sgpt-ai/k8sgpt/commit/02e754ed591742fccc5ff9a20c3e36e4475f6ec5))
|
||||
* add Ollama backend ([#1065](https://github.com/k8sgpt-ai/k8sgpt/issues/1065)) ([b35dbd9](https://github.com/k8sgpt-ai/k8sgpt/commit/b35dbd9b09197994f041cda04f1a4e5fb316e468))
|
||||
* add watsonx ai provider ([#1163](https://github.com/k8sgpt-ai/k8sgpt/issues/1163)) ([ce63821](https://github.com/k8sgpt-ai/k8sgpt/commit/ce63821bebbd87b2e058f5cf58a2cdd474b8fb58))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 to v2.20.0-20240406062209-1cc152efbf5c.1 ([#1147](https://github.com/k8sgpt-ai/k8sgpt/issues/1147)) ([314f25a](https://github.com/k8sgpt-ai/k8sgpt/commit/314f25ac8bf5c3629474ece0eae6a3bda83099aa))
|
||||
* **deps:** update module github.com/mittwald/go-helm-client to v0.12.10 ([#1177](https://github.com/k8sgpt-ai/k8sgpt/issues/1177)) ([fef8539](https://github.com/k8sgpt-ai/k8sgpt/commit/fef853966fc6e33dae0a9686fa767b36201c0228))
|
||||
* **deps:** update module github.com/spf13/cobra to v1.8.1 ([#1161](https://github.com/k8sgpt-ai/k8sgpt/issues/1161)) ([a075792](https://github.com/k8sgpt-ai/k8sgpt/commit/a0757921191205398539a6ccc8dbfaa503db595f))
|
||||
* **deps:** update module google.golang.org/grpc to v1.64.1 [security] ([#1178](https://github.com/k8sgpt-ai/k8sgpt/issues/1178)) ([dd20dbc](https://github.com/k8sgpt-ai/k8sgpt/commit/dd20dbc9829fc50f77ad6a32c3a10dcf221d2750))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update amannn/action-semantic-pull-request action to v5.5.3 ([#1172](https://github.com/k8sgpt-ai/k8sgpt/issues/1172)) ([27ac60a](https://github.com/k8sgpt-ai/k8sgpt/commit/27ac60aed296c3d9582f34e14c5985a4bccd991e))
|
||||
* **deps:** update anchore/sbom-action action to v0.16.0 ([#1146](https://github.com/k8sgpt-ai/k8sgpt/issues/1146)) ([dd66355](https://github.com/k8sgpt-ai/k8sgpt/commit/dd6635579789ce65ee86dc1196e7dfde1b7d20e6))
|
||||
* **deps:** update docker/build-push-action digest to ca052bb ([#1140](https://github.com/k8sgpt-ai/k8sgpt/issues/1140)) ([0c02160](https://github.com/k8sgpt-ai/k8sgpt/commit/0c0216096efde9c2c812ee90522c081f51c52631))
|
||||
* **deps:** update docker/setup-buildx-action digest to 4fd8129 ([#1173](https://github.com/k8sgpt-ai/k8sgpt/issues/1173)) ([d4abb33](https://github.com/k8sgpt-ai/k8sgpt/commit/d4abb33b3c29d9a2e4dee094ea7be2bc5d1807d1))
|
||||
* update brew installation note ([#1155](https://github.com/k8sgpt-ai/k8sgpt/issues/1155)) ([ab534d1](https://github.com/k8sgpt-ai/k8sgpt/commit/ab534d184fcd538f2ba10a6b5bf3a74c28d5fee6))
|
||||
|
||||
## [0.3.37](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.36...v0.3.37) (2024-06-17)
|
||||
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -8,12 +8,12 @@
|
||||

|
||||

|
||||
[](https://bestpractices.coreinfrastructure.org/projects/7272)
|
||||
[](https://docs.k8sgpt.ai/)
|
||||
[](https://docs.k8sgpt.ai/)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fk8sgpt-ai%2Fk8sgpt?ref=badge_shield)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://github.com/k8sgpt-ai/k8sgpt)
|
||||
[](https://codecov.io/github/k8sgpt-ai/k8sgpt)
|
||||

|
||||

|
||||
|
||||
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
|
||||
|
||||
@@ -30,7 +30,13 @@ _Out of the box integration with OpenAI, Azure, Cohere, Amazon Bedrock, Google G
|
||||
|
||||
### Linux/Mac via brew
|
||||
|
||||
```sh
|
||||
$ brew install k8sgpt
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
brew tap k8sgpt-ai/k8sgpt
|
||||
brew install k8sgpt
|
||||
```
|
||||
@@ -41,7 +47,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.37/k8sgpt_386.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/k8sgpt_386.rpm
|
||||
sudo rpm -ivh k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -50,7 +56,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.37/k8sgpt_amd64.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh -i k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -62,7 +68,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.37/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -70,7 +76,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.37/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -83,14 +89,14 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.37/k8sgpt_386.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/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.37/k8sgpt_amd64.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.40/k8sgpt_amd64.apk
|
||||
apk add k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->x
|
||||
@@ -293,6 +299,12 @@ _Analysis with serve mode_
|
||||
```
|
||||
grpcurl -plaintext -d '{"namespace": "k8sgpt", "explain": false}' localhost:8080 schema.v1.ServerService/Analyze
|
||||
```
|
||||
|
||||
_Analysis with custom headers_
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --custom-headers CustomHeaderKey:CustomHeaderValue
|
||||
```
|
||||
</details>
|
||||
|
||||
## LLM AI Backends
|
||||
@@ -302,12 +314,13 @@ K8sGPT uses the chosen LLM, generative AI provider when you want to explain the
|
||||
You can list available providers using `k8sgpt auth list`:
|
||||
|
||||
```
|
||||
Default:
|
||||
Default:
|
||||
> openai
|
||||
Active:
|
||||
Unused:
|
||||
Active:
|
||||
Unused:
|
||||
> openai
|
||||
> localai
|
||||
> ollama
|
||||
> azureopenai
|
||||
> cohere
|
||||
> amazonbedrock
|
||||
@@ -316,6 +329,7 @@ Unused:
|
||||
> huggingface
|
||||
> noopai
|
||||
> googlevertexai
|
||||
> watsonxai
|
||||
```
|
||||
|
||||
For detailed documentation on how to configure and use each provider see [here](https://docs.k8sgpt.ai/reference/providers/backend/).
|
||||
@@ -425,7 +439,7 @@ Config file locations:
|
||||
There may be scenarios where caching remotely is preferred.
|
||||
In these scenarios K8sGPT supports AWS S3 or Azure Blob storage Integration.
|
||||
|
||||
<summary> Remote caching </summary>
|
||||
<summary> Remote caching </summary>
|
||||
<em>Note: You can only configure and use only one remote cache at a time</em>
|
||||
|
||||
_Adding a remote cache_
|
||||
@@ -440,11 +454,11 @@ _Adding a remote cache_
|
||||
* We support a number of [techniques](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#2-authenticate-with-azure) to authenticate against Azure
|
||||
* Configuration, ``` k8sgpt cache add azure --storageacc <storage account name> --container <container name> ```
|
||||
* K8sGPT assumes that the storage account already exist and it will create the container if it does not exist
|
||||
* It is the **user** responsibility have to grant specific permissions to their identity in order to be able to upload blob files and create SA containers (e.g Storage Blob Data Contributor)
|
||||
* It is the **user** responsibility have to grant specific permissions to their identity in order to be able to upload blob files and create SA containers (e.g Storage Blob Data Contributor)
|
||||
* Google Cloud Storage
|
||||
* _As a prerequisite `GOOGLE_APPLICATION_CREDENTIALS` are required as environmental variables._
|
||||
* Configuration, ``` k8sgpt cache add gcs --region <gcp region> --bucket <name> --projectid <project id>```
|
||||
* K8sGPT will create the bucket if it does not exist
|
||||
* K8sGPT will create the bucket if it does not exist
|
||||
|
||||
_Listing cache items_
|
||||
```
|
||||
|
||||
@@ -33,11 +33,13 @@ var (
|
||||
language string
|
||||
nocache bool
|
||||
namespace string
|
||||
labelSelector string
|
||||
anonymize bool
|
||||
maxConcurrency int
|
||||
withDoc bool
|
||||
interactiveMode bool
|
||||
customAnalysis bool
|
||||
customHeaders []string
|
||||
)
|
||||
|
||||
// AnalyzeCmd represents the problems command
|
||||
@@ -54,11 +56,13 @@ var AnalyzeCmd = &cobra.Command{
|
||||
language,
|
||||
filters,
|
||||
namespace,
|
||||
labelSelector,
|
||||
nocache,
|
||||
explain,
|
||||
maxConcurrency,
|
||||
withDoc,
|
||||
interactiveMode,
|
||||
customHeaders,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -138,5 +142,8 @@ func init() {
|
||||
AnalyzeCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Enable interactive mode that allows further conversation with LLM about the problem. Works only with --explain flag")
|
||||
// custom analysis flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&customAnalysis, "custom-analysis", "z", false, "Enable custom analyzers")
|
||||
|
||||
// add custom headers flag
|
||||
AnalyzeCmd.Flags().StringSliceVarP(&customHeaders, "custom-headers", "r", []string{}, "Custom Headers, <key>:<value> (e.g CustomHeaderKey:CustomHeaderValue AnotherHeader:AnotherValue)")
|
||||
// label selector flag
|
||||
AnalyzeCmd.Flags().StringVarP(&labelSelector, "selector", "L", "", "Label selector (label query) to filter on, supports '=', '==', and '!='. (e.g. -L key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ var updateCmd = &cobra.Command{
|
||||
Long: "The command to update an AI backend provider",
|
||||
// Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
_ = cmd.MarkFlagRequired("backend")
|
||||
backend, _ := cmd.Flags().GetString("backend")
|
||||
if strings.ToLower(backend) == "azureopenai" {
|
||||
_ = cmd.MarkFlagRequired("engine")
|
||||
|
||||
56
go.mod
56
go.mod
@@ -9,13 +9,14 @@ require (
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/kedacore/keda/v2 v2.11.2
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mittwald/go-helm-client v0.12.9
|
||||
github.com/mittwald/go-helm-client v0.12.10
|
||||
github.com/ollama/ollama v0.1.48
|
||||
github.com/sashabaranov/go-openai v1.23.0
|
||||
github.com/schollz/progressbar/v3 v3.14.2
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/schollz/progressbar/v3 v3.14.5
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/term v0.21.0
|
||||
golang.org/x/term v0.22.0
|
||||
helm.sh/helm/v3 v3.15.2
|
||||
k8s.io/api v0.30.2
|
||||
k8s.io/apimachinery v0.30.2
|
||||
@@ -27,23 +28,25 @@ require (
|
||||
require github.com/adrg/xdg v0.4.0
|
||||
|
||||
require (
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.19.1-20240406062209-1cc152efbf5c.1
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240406062209-1cc152efbf5c.3
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.34.0-20240406062209-1cc152efbf5c.1
|
||||
cloud.google.com/go/storage v1.40.0
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.20.0-20240406062209-1cc152efbf5c.1
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.4.0-20240720172138-1b9bcd834f17.2
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.34.2-20240720172138-1b9bcd834f17.2
|
||||
cloud.google.com/go/storage v1.43.0
|
||||
cloud.google.com/go/vertexai v0.7.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
|
||||
github.com/IBM/watsonx-go v1.0.1
|
||||
github.com/aws/aws-sdk-go v1.53.21
|
||||
github.com/cohere-ai/cohere-go/v2 v2.7.3
|
||||
github.com/google/generative-ai-go v0.11.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0
|
||||
github.com/hupe1980/go-huggingface v0.0.15
|
||||
github.com/kyverno/policy-reporter-kyverno-plugin v1.6.3
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/oracle/oci-go-sdk/v65 v65.65.1
|
||||
github.com/prometheus/prometheus v0.49.1
|
||||
github.com/prometheus/prometheus v0.53.1
|
||||
github.com/pterm/pterm v0.12.79
|
||||
google.golang.org/api v0.172.0
|
||||
google.golang.org/api v0.187.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
sigs.k8s.io/controller-runtime v0.18.4
|
||||
sigs.k8s.io/gateway-api v1.0.0
|
||||
@@ -53,12 +56,14 @@ require (
|
||||
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.112.1 // indirect
|
||||
cloud.google.com/go v0.115.0 // indirect
|
||||
cloud.google.com/go/ai v0.3.5-0.20240409161017-ce55ad694f21 // indirect
|
||||
cloud.google.com/go/aiplatform v1.60.0 // indirect
|
||||
cloud.google.com/go/aiplatform v1.68.0 // indirect
|
||||
cloud.google.com/go/auth v0.6.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.6 // indirect
|
||||
cloud.google.com/go/iam v1.1.8 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
@@ -80,21 +85,21 @@ require (
|
||||
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.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gorilla/websocket v1.5.2 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect
|
||||
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // 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/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // 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/segmentio/fasthash v1.0.3 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
@@ -103,9 +108,9 @@ require (
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
knative.dev/pkg v0.0.0-20230616134650-eb63a40adfb0 // indirect
|
||||
@@ -196,7 +201,7 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
@@ -230,11 +235,11 @@ require (
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/grpc v1.64.1
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
@@ -244,7 +249,7 @@ require (
|
||||
k8s.io/component-base v0.30.2 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
|
||||
oras.land/oras-go v1.2.5 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.17.2 // indirect
|
||||
@@ -256,3 +261,4 @@ require (
|
||||
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
|
||||
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
|
||||
//replace oras.land/oras-go => oras.land/oras-go v1.2.4
|
||||
replace github.com/docker/docker => github.com/docker/docker v27.1.1+incompatible
|
||||
|
||||
@@ -15,6 +15,7 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +23,7 @@ var (
|
||||
&OpenAIClient{},
|
||||
&AzureAIClient{},
|
||||
&LocalAIClient{},
|
||||
&OllamaClient{},
|
||||
&NoOpAIClient{},
|
||||
&CohereClient{},
|
||||
&AmazonBedRockClient{},
|
||||
@@ -30,10 +32,12 @@ var (
|
||||
&HuggingfaceClient{},
|
||||
&GoogleVertexAIClient{},
|
||||
&OCIGenAIClient{},
|
||||
&WatsonxAIClient{},
|
||||
}
|
||||
Backends = []string{
|
||||
openAIClientName,
|
||||
localAIClientName,
|
||||
ollamaClientName,
|
||||
azureAIClientName,
|
||||
cohereAIClientName,
|
||||
amazonbedrockAIClientName,
|
||||
@@ -43,6 +47,7 @@ var (
|
||||
huggingfaceAIClientName,
|
||||
googleVertexAIClientName,
|
||||
ociClientName,
|
||||
watsonxAIClientName,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -79,6 +84,7 @@ type IAIConfig interface {
|
||||
GetProviderId() string
|
||||
GetCompartmentId() string
|
||||
GetOrganizationId() string
|
||||
GetCustomHeaders() []http.Header
|
||||
}
|
||||
|
||||
func NewClient(provider string) IAI {
|
||||
@@ -97,22 +103,23 @@ type AIConfiguration struct {
|
||||
}
|
||||
|
||||
type AIProvider struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Model string `mapstructure:"model"`
|
||||
Password string `mapstructure:"password" yaml:"password,omitempty"`
|
||||
BaseURL string `mapstructure:"baseurl" yaml:"baseurl,omitempty"`
|
||||
ProxyEndpoint string `mapstructure:"proxyEndpoint" yaml:"proxyEndpoint,omitempty"`
|
||||
ProxyPort string `mapstructure:"proxyPort" yaml:"proxyPort,omitempty"`
|
||||
EndpointName string `mapstructure:"endpointname" yaml:"endpointname,omitempty"`
|
||||
Engine string `mapstructure:"engine" yaml:"engine,omitempty"`
|
||||
Temperature float32 `mapstructure:"temperature" yaml:"temperature,omitempty"`
|
||||
ProviderRegion string `mapstructure:"providerregion" yaml:"providerregion,omitempty"`
|
||||
ProviderId string `mapstructure:"providerid" yaml:"providerid,omitempty"`
|
||||
CompartmentId string `mapstructure:"compartmentid" yaml:"compartmentid,omitempty"`
|
||||
TopP float32 `mapstructure:"topp" yaml:"topp,omitempty"`
|
||||
TopK int32 `mapstructure:"topk" yaml:"topk,omitempty"`
|
||||
MaxTokens int `mapstructure:"maxtokens" yaml:"maxtokens,omitempty"`
|
||||
OrganizationId string `mapstructure:"organizationid" yaml:"organizationid,omitempty"`
|
||||
Name string `mapstructure:"name"`
|
||||
Model string `mapstructure:"model"`
|
||||
Password string `mapstructure:"password" yaml:"password,omitempty"`
|
||||
BaseURL string `mapstructure:"baseurl" yaml:"baseurl,omitempty"`
|
||||
ProxyEndpoint string `mapstructure:"proxyEndpoint" yaml:"proxyEndpoint,omitempty"`
|
||||
ProxyPort string `mapstructure:"proxyPort" yaml:"proxyPort,omitempty"`
|
||||
EndpointName string `mapstructure:"endpointname" yaml:"endpointname,omitempty"`
|
||||
Engine string `mapstructure:"engine" yaml:"engine,omitempty"`
|
||||
Temperature float32 `mapstructure:"temperature" yaml:"temperature,omitempty"`
|
||||
ProviderRegion string `mapstructure:"providerregion" yaml:"providerregion,omitempty"`
|
||||
ProviderId string `mapstructure:"providerid" yaml:"providerid,omitempty"`
|
||||
CompartmentId string `mapstructure:"compartmentid" yaml:"compartmentid,omitempty"`
|
||||
TopP float32 `mapstructure:"topp" yaml:"topp,omitempty"`
|
||||
TopK int32 `mapstructure:"topk" yaml:"topk,omitempty"`
|
||||
MaxTokens int `mapstructure:"maxtokens" yaml:"maxtokens,omitempty"`
|
||||
OrganizationId string `mapstructure:"organizationid" yaml:"organizationid,omitempty"`
|
||||
CustomHeaders []http.Header `mapstructure:"customHeaders"`
|
||||
}
|
||||
|
||||
func (p *AIProvider) GetBaseURL() string {
|
||||
@@ -170,7 +177,11 @@ func (p *AIProvider) GetOrganizationId() string {
|
||||
return p.OrganizationId
|
||||
}
|
||||
|
||||
var passwordlessProviders = []string{"localai", "amazonsagemaker", "amazonbedrock", "googlevertexai", "oci"}
|
||||
func (p *AIProvider) GetCustomHeaders() []http.Header {
|
||||
return p.CustomHeaders
|
||||
}
|
||||
|
||||
var passwordlessProviders = []string{"localai", "ollama", "amazonsagemaker", "amazonbedrock", "googlevertexai", "oci", "watsonxai"}
|
||||
|
||||
func NeedPassword(backend string) bool {
|
||||
for _, b := range passwordlessProviders {
|
||||
|
||||
102
pkg/ai/ollama.go
Normal file
102
pkg/ai/ollama.go
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
ollama "github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
const ollamaClientName = "ollama"
|
||||
|
||||
type OllamaClient struct {
|
||||
nopCloser
|
||||
|
||||
client *ollama.Client
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
}
|
||||
|
||||
const (
|
||||
defaultBaseURL = "http://localhost:11434"
|
||||
defaultModel = "llama3"
|
||||
)
|
||||
|
||||
func (c *OllamaClient) Configure(config IAIConfig) error {
|
||||
baseURL := config.GetBaseURL()
|
||||
if baseURL == "" {
|
||||
baseURL = defaultBaseURL
|
||||
}
|
||||
baseClientURL, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxyEndpoint := config.GetProxyEndpoint()
|
||||
httpClient := http.DefaultClient
|
||||
if proxyEndpoint != "" {
|
||||
proxyUrl, err := url.Parse(proxyEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyUrl),
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
c.client = ollama.NewClient(baseClientURL, httpClient)
|
||||
if c.client == nil {
|
||||
return errors.New("error creating Ollama client")
|
||||
}
|
||||
c.model = config.GetModel()
|
||||
if c.model == "" {
|
||||
c.model = defaultModel
|
||||
}
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
return nil
|
||||
}
|
||||
func (c *OllamaClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
req := &ollama.GenerateRequest{
|
||||
Model: c.model,
|
||||
Prompt: prompt,
|
||||
Stream: new(bool),
|
||||
Options: map[string]interface{}{
|
||||
"temperature": c.temperature,
|
||||
"top_p": c.topP,
|
||||
},
|
||||
}
|
||||
completion := ""
|
||||
respFunc := func(resp ollama.GenerateResponse) error {
|
||||
completion = resp.Response
|
||||
return nil
|
||||
}
|
||||
err := c.client.Generate(ctx, req, respFunc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return completion, nil
|
||||
}
|
||||
func (a *OllamaClient) GetName() string {
|
||||
return ollamaClientName
|
||||
}
|
||||
@@ -52,24 +52,27 @@ func (c *OpenAIClient) Configure(config IAIConfig) error {
|
||||
defaultConfig.BaseURL = baseURL
|
||||
}
|
||||
|
||||
transport := &http.Transport{}
|
||||
if proxyEndpoint != "" {
|
||||
proxyUrl, err := url.Parse(proxyEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyUrl),
|
||||
}
|
||||
|
||||
defaultConfig.HTTPClient = &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
transport.Proxy = http.ProxyURL(proxyUrl)
|
||||
}
|
||||
|
||||
if orgId != "" {
|
||||
defaultConfig.OrgID = orgId
|
||||
}
|
||||
|
||||
customHeaders := config.GetCustomHeaders()
|
||||
defaultConfig.HTTPClient = &http.Client{
|
||||
Transport: &OpenAIHeaderTransport{
|
||||
Origin: transport,
|
||||
Headers: customHeaders,
|
||||
},
|
||||
}
|
||||
|
||||
client := openai.NewClientWithConfig(defaultConfig)
|
||||
if client == nil {
|
||||
return errors.New("error creating OpenAI client")
|
||||
@@ -106,3 +109,25 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
|
||||
func (c *OpenAIClient) GetName() string {
|
||||
return openAIClientName
|
||||
}
|
||||
|
||||
// OpenAIHeaderTransport is an http.RoundTripper that adds the given headers to each request.
|
||||
type OpenAIHeaderTransport struct {
|
||||
Origin http.RoundTripper
|
||||
Headers []http.Header
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface.
|
||||
func (t *OpenAIHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Clone the request to avoid modifying the original request
|
||||
clonedReq := req.Clone(req.Context())
|
||||
for _, header := range t.Headers {
|
||||
for key, values := range header {
|
||||
// Possible values per header: RFC 2616
|
||||
for _, value := range values {
|
||||
clonedReq.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t.Origin.RoundTrip(clonedReq)
|
||||
}
|
||||
|
||||
106
pkg/ai/openai_header_transport_test.go
Normal file
106
pkg/ai/openai_header_transport_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Mock configuration
|
||||
type mockConfig struct {
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetPassword() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetOrganizationId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetProxyEndpoint() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetBaseURL() string {
|
||||
return m.baseURL
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetCustomHeaders() []http.Header {
|
||||
return []http.Header{
|
||||
{"X-Custom-Header-1": []string{"Value1"}},
|
||||
{"X-Custom-Header-2": []string{"Value2"}},
|
||||
{"X-Custom-Header-2": []string{"Value3"}}, // Testing multiple values for the same header
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetModel() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetTemperature() float32 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetTopP() float32 {
|
||||
return 0.0
|
||||
}
|
||||
func (m *mockConfig) GetCompartmentId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetTopK() int32 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetMaxTokens() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetEndpointName() string {
|
||||
return ""
|
||||
}
|
||||
func (m *mockConfig) GetEngine() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetProviderId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *mockConfig) GetProviderRegion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestOpenAIClient_CustomHeaders(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "Value1", r.Header.Get("X-Custom-Header-1"))
|
||||
assert.ElementsMatch(t, []string{"Value2", "Value3"}, r.Header["X-Custom-Header-2"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Mock response for openai completion
|
||||
mockResponse := `{"choices": [{"message": {"content": "test"}}]}`
|
||||
n, err := w.Write([]byte(mockResponse))
|
||||
if err != nil {
|
||||
t.Fatalf("error writing response: %v", err)
|
||||
}
|
||||
if n != len(mockResponse) {
|
||||
t.Fatalf("expected to write %d bytes but wrote %d bytes", len(mockResponse), n)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
config := &mockConfig{baseURL: server.URL}
|
||||
|
||||
client := &OpenAIClient{}
|
||||
err := client.Configure(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Make a completion request to trigger the headers
|
||||
ctx := context.Background()
|
||||
_, err = client.GetCompletion(ctx, "foo prompt")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -48,6 +48,16 @@ const (
|
||||
- Containers:
|
||||
- {list of container names}
|
||||
`
|
||||
|
||||
kyverno_prompt = `Simplify the following Kyverno warnings message delimited by triple dashes written in --- %s --- language; --- %s ---.
|
||||
Provide the most probable solution as a kubectl command.
|
||||
|
||||
Write the output in the following format, for the solution, only show the kubectl command:
|
||||
|
||||
Error: {Explain error here}
|
||||
|
||||
Solution: {kubectl command}
|
||||
`
|
||||
)
|
||||
|
||||
var PromptMap = map[string]string{
|
||||
@@ -56,4 +66,6 @@ var PromptMap = map[string]string{
|
||||
"ConfigAuditReport": trivy_conf_prompt,
|
||||
"PrometheusConfigValidate": prom_conf_prompt,
|
||||
"PrometheusConfigRelabelReport": prom_relabel_prompt,
|
||||
"PolicyReport": kyverno_prompt,
|
||||
"ClusterPolicyReport": kyverno_prompt,
|
||||
}
|
||||
|
||||
88
pkg/ai/watsonxai.go
Normal file
88
pkg/ai/watsonxai.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
wx "github.com/IBM/watsonx-go/pkg/models"
|
||||
)
|
||||
|
||||
const watsonxAIClientName = "watsonxai"
|
||||
|
||||
type WatsonxAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *wx.Client
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
topK int32
|
||||
maxNewTokens int
|
||||
}
|
||||
|
||||
const (
|
||||
modelMetallama = "ibm/granite-13b-chat-v2"
|
||||
maxTokens = 2048
|
||||
)
|
||||
|
||||
func (c *WatsonxAIClient) Configure(config IAIConfig) error {
|
||||
if config.GetModel() == "" {
|
||||
c.model = modelMetallama
|
||||
} else {
|
||||
c.model = config.GetModel()
|
||||
}
|
||||
if config.GetMaxTokens() == 0 {
|
||||
c.maxNewTokens = maxTokens
|
||||
} else {
|
||||
c.maxNewTokens = config.GetMaxTokens()
|
||||
}
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
|
||||
// WatsonxAPIKeyEnvVarName = "WATSONX_API_KEY"
|
||||
// WatsonxProjectIDEnvVarName = "WATSONX_PROJECT_ID"
|
||||
apiKey, projectID := os.Getenv(wx.WatsonxAPIKeyEnvVarName), os.Getenv(wx.WatsonxProjectIDEnvVarName)
|
||||
|
||||
if apiKey == "" {
|
||||
return errors.New("No watsonx API key provided")
|
||||
}
|
||||
if projectID == "" {
|
||||
return errors.New("No watsonx project ID provided")
|
||||
}
|
||||
|
||||
client, err := wx.NewClient(
|
||||
wx.WithWatsonxAPIKey(apiKey),
|
||||
wx.WithWatsonxProjectID(projectID),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create client for testing. Error: %v", err)
|
||||
}
|
||||
c.client = client
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *WatsonxAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
result, err := c.client.GenerateText(
|
||||
c.model,
|
||||
prompt,
|
||||
wx.WithTemperature((float64)(c.temperature)),
|
||||
wx.WithTopP((float64)(c.topP)),
|
||||
wx.WithTopK((uint)(c.topK)),
|
||||
wx.WithMaxNewTokens((uint)(c.maxNewTokens)),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Expected no error, but got an error: %v", err)
|
||||
}
|
||||
if result.Text == "" {
|
||||
return "", errors.New("Expected a result, but got an empty string")
|
||||
}
|
||||
return result.Text, nil
|
||||
}
|
||||
|
||||
func (c *WatsonxAIClient) GetName() string {
|
||||
return watsonxAIClientName
|
||||
}
|
||||
@@ -44,6 +44,7 @@ type Analysis struct {
|
||||
Results []common.Result
|
||||
Errors []string
|
||||
Namespace string
|
||||
LabelSelector string
|
||||
Cache cache.ICache
|
||||
Explain bool
|
||||
MaxConcurrency int
|
||||
@@ -74,11 +75,13 @@ func NewAnalysis(
|
||||
language string,
|
||||
filters []string,
|
||||
namespace string,
|
||||
labelSelector string,
|
||||
noCache bool,
|
||||
explain bool,
|
||||
maxConcurrency int,
|
||||
withDoc bool,
|
||||
interactiveMode bool,
|
||||
httpHeaders []string,
|
||||
) (*Analysis, error) {
|
||||
// Get kubernetes client from viper.
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
@@ -104,6 +107,7 @@ func NewAnalysis(
|
||||
Client: client,
|
||||
Language: language,
|
||||
Namespace: namespace,
|
||||
LabelSelector: labelSelector,
|
||||
Cache: cache,
|
||||
Explain: explain,
|
||||
MaxConcurrency: maxConcurrency,
|
||||
@@ -146,6 +150,8 @@ func NewAnalysis(
|
||||
}
|
||||
|
||||
aiClient := ai.NewClient(aiProvider.Name)
|
||||
customHeaders := util.NewHeaders(httpHeaders)
|
||||
aiProvider.CustomHeaders = customHeaders
|
||||
if err := aiClient.Configure(&aiProvider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -154,25 +160,57 @@ func NewAnalysis(
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Analysis) CustomAnalyzersAreAvailable() bool {
|
||||
var customAnalyzers []custom.CustomAnalyzer
|
||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||
return false
|
||||
}
|
||||
return len(customAnalyzers) > 0
|
||||
}
|
||||
|
||||
func (a *Analysis) RunCustomAnalysis() {
|
||||
var customAnalyzers []custom.CustomAnalyzer
|
||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||
a.Errors = append(a.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
semaphore := make(chan struct{}, a.MaxConcurrency)
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
for _, cAnalyzer := range customAnalyzers {
|
||||
wg.Add(1)
|
||||
semaphore <- struct{}{}
|
||||
go func(analyzer custom.CustomAnalyzer, wg *sync.WaitGroup, semaphore chan struct{}) {
|
||||
defer wg.Done()
|
||||
canClient, err := custom.NewClient(cAnalyzer.Connection)
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("Client creation error for %s analyzer", cAnalyzer.Name))
|
||||
mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
canClient, err := custom.NewClient(cAnalyzer.Connection)
|
||||
if err != nil {
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("Client creation error for %s analyzer", cAnalyzer.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
result, err := canClient.Run()
|
||||
if err != nil {
|
||||
a.Results = append(a.Results, result)
|
||||
}
|
||||
result, err := canClient.Run()
|
||||
if result.Kind == "" {
|
||||
// for custom analyzer name, we must use a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.',
|
||||
//and must start and end with an alphanumeric character (e.g. 'example.com',
|
||||
//regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')
|
||||
result.Kind = cAnalyzer.Name
|
||||
}
|
||||
if err != nil {
|
||||
mutex.Lock()
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", cAnalyzer.Name, err))
|
||||
mutex.Unlock()
|
||||
} else {
|
||||
mutex.Lock()
|
||||
a.Results = append(a.Results, result)
|
||||
mutex.Unlock()
|
||||
}
|
||||
<-semaphore
|
||||
}(cAnalyzer, &wg, semaphore)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (a *Analysis) RunAnalysis() {
|
||||
@@ -195,15 +233,16 @@ func (a *Analysis) RunAnalysis() {
|
||||
Client: a.Client,
|
||||
Context: a.Context,
|
||||
Namespace: a.Namespace,
|
||||
LabelSelector: a.LabelSelector,
|
||||
AIClient: a.AIClient,
|
||||
OpenapiSchema: openapiSchema,
|
||||
}
|
||||
|
||||
semaphore := make(chan struct{}, a.MaxConcurrency)
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
// if there are no filters selected and no active_filters then run coreAnalyzer
|
||||
if len(a.Filters) == 0 && len(activeFilters) == 0 {
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
for _, analyzer := range coreAnalyzerMap {
|
||||
wg.Add(1)
|
||||
semaphore <- struct{}{}
|
||||
@@ -225,11 +264,8 @@ func (a *Analysis) RunAnalysis() {
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
semaphore = make(chan struct{}, a.MaxConcurrency)
|
||||
// if the filters flag is specified
|
||||
if len(a.Filters) != 0 {
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
for _, filter := range a.Filters {
|
||||
if analyzer, ok := analyzerMap[filter]; ok {
|
||||
semaphore <- struct{}{}
|
||||
@@ -255,9 +291,6 @@ func (a *Analysis) RunAnalysis() {
|
||||
return
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mutex sync.Mutex
|
||||
semaphore = make(chan struct{}, a.MaxConcurrency)
|
||||
// use active_filters
|
||||
for _, filter := range activeFilters {
|
||||
if analyzer, ok := analyzerMap[filter]; ok {
|
||||
|
||||
@@ -43,7 +43,7 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{})
|
||||
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -156,3 +156,45 @@ func TestCronJobAnalyzer(t *testing.T) {
|
||||
require.Equal(t, expectations[i], result.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
suspend := new(bool)
|
||||
*suspend = true
|
||||
|
||||
invalidStartingDeadline := new(int64)
|
||||
*invalidStartingDeadline = -7
|
||||
|
||||
validStartingDeadline := new(int64)
|
||||
*validStartingDeadline = 7
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "cronjob",
|
||||
},
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=cronjob",
|
||||
}
|
||||
|
||||
cjAnalyzer := CronJobAnalyzer{}
|
||||
results, err := cjAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/CJ1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{})
|
||||
deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -151,3 +151,55 @@ func TestDeploymentAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
assert.Equal(t, analysisResults[0].Kind, "Deployment")
|
||||
assert.Equal(t, analysisResults[0].Name, "default/example")
|
||||
}
|
||||
|
||||
func TestDeploymentAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "deployment",
|
||||
},
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: func() *int32 { i := int32(3); return &i }(),
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: func() *int32 { i := int32(3); return &i }(),
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=deployment",
|
||||
}
|
||||
|
||||
deploymentAnalyzer := DeploymentAnalyzer{}
|
||||
analysisResults, err := deploymentAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ func (GatewayAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.List(a.Context, gtwList, &ctrl.ListOptions{}); err != nil {
|
||||
|
||||
labelSelector := util.LabelStrToSelector(a.LabelSelector)
|
||||
if err := client.List(a.Context, gtwList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,13 @@ func BuildGatewayClass(name string) gtwapi.GatewayClass {
|
||||
return GatewayClass
|
||||
}
|
||||
|
||||
func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus) gtwapi.Gateway {
|
||||
func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus, labels map[string]string) gtwapi.Gateway {
|
||||
Gateway := gtwapi.Gateway{}
|
||||
Gateway.Name = "foobar"
|
||||
Gateway.Namespace = "default"
|
||||
if labels != nil {
|
||||
Gateway.Labels = labels
|
||||
}
|
||||
Gateway.Spec.GatewayClassName = className
|
||||
Gateway.Spec.Listeners = []gtwapi.Listener{
|
||||
{
|
||||
@@ -53,7 +56,7 @@ func TestGatewayAnalyzer(t *testing.T) {
|
||||
AcceptedStatus := metav1.ConditionTrue
|
||||
GatewayClass := BuildGatewayClass(string(ClassName))
|
||||
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
|
||||
@@ -91,7 +94,7 @@ func TestGatewayAnalyzer(t *testing.T) {
|
||||
func TestMissingClassGatewayAnalyzer(t *testing.T) {
|
||||
ClassName := gtwapi.ObjectName("non-existed")
|
||||
AcceptedStatus := metav1.ConditionTrue
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
|
||||
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
@@ -130,7 +133,7 @@ func TestStatusGatewayAnalyzer(t *testing.T) {
|
||||
AcceptedStatus := metav1.ConditionUnknown
|
||||
GatewayClass := BuildGatewayClass(string(ClassName))
|
||||
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus)
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
|
||||
|
||||
// Create a Gateway Analyzer instance with the fake client
|
||||
scheme := scheme.Scheme
|
||||
@@ -178,3 +181,70 @@ func TestStatusGatewayAnalyzer(t *testing.T) {
|
||||
t.Errorf("Expected message, <%v> , not found in Gateway's analysis results", want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
ClassName := gtwapi.ObjectName("non-existed")
|
||||
AcceptedStatus := metav1.ConditionTrue
|
||||
|
||||
Gateway := BuildGateway(ClassName, AcceptedStatus, map[string]string{"app": "gateway"})
|
||||
scheme := scheme.Scheme
|
||||
err := gtwapi.Install(scheme)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = apiextensionsv1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
objects := []runtime.Object{
|
||||
&Gateway,
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
|
||||
|
||||
analyzerInstance := GatewayAnalyzer{}
|
||||
// without label selector should return 1 result
|
||||
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)
|
||||
|
||||
// with label selector should return 1 result
|
||||
config = common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=gateway",
|
||||
}
|
||||
analysisResults, err = analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
// with wrong label selector should return 0 result
|
||||
config = common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=wrong",
|
||||
}
|
||||
analysisResults, err = analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ func (GatewayClassAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.List(a.Context, gcList, &ctrl.ListOptions{}); err != nil {
|
||||
|
||||
labelSelector := util.LabelStrToSelector(a.LabelSelector)
|
||||
if err := client.List(a.Context, gcList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
@@ -55,3 +55,51 @@ func TestGatewayClassAnalyzer(t *testing.T) {
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
|
||||
func TestGatewayClassAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
condition := metav1.Condition{
|
||||
Type: "Accepted",
|
||||
Status: "Ready",
|
||||
Message: "Ready",
|
||||
Reason: "Ready",
|
||||
}
|
||||
|
||||
// Create two GatewayClasses with different labels
|
||||
GatewayClass := >wapi.GatewayClass{}
|
||||
GatewayClass.Name = "foobar"
|
||||
GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
|
||||
GatewayClass.Labels = map[string]string{"app": "gatewayclass"}
|
||||
GatewayClass.Status.Conditions = []metav1.Condition{condition}
|
||||
|
||||
GatewayClass2 := >wapi.GatewayClass{}
|
||||
GatewayClass2.Name = "foobar2"
|
||||
GatewayClass2.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
|
||||
GatewayClass2.Status.Conditions = []metav1.Condition{condition}
|
||||
|
||||
scheme := scheme.Scheme
|
||||
err := gtwapi.Install(scheme)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = apiextensionsv1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass, GatewayClass2).Build()
|
||||
|
||||
analyzerInstance := GatewayClassAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
CtrlClient: fakeClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=gatewayclass",
|
||||
}
|
||||
analysisResults, err := analyzerInstance.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().AutoscalingV2().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,6 +53,18 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
for _, hpa := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
|
||||
//check the error from status field
|
||||
conditions := hpa.Status.Conditions
|
||||
for _, condition := range conditions {
|
||||
if condition.Status != "True" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: condition.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// check ScaleTargetRef exist
|
||||
scaleTargetRef := hpa.Spec.ScaleTargetRef
|
||||
var podInfo PodInfo
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
func TestHPAAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
@@ -55,14 +55,14 @@ func TestHPAAnalyzer(t *testing.T) {
|
||||
|
||||
func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-2",
|
||||
Namespace: "default",
|
||||
@@ -88,14 +88,14 @@ func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
|
||||
func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "unsupported",
|
||||
},
|
||||
},
|
||||
@@ -134,14 +134,14 @@ func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
|
||||
func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "non-existent",
|
||||
},
|
||||
@@ -181,14 +181,14 @@ func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
@@ -245,14 +245,14 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsDeployment(t *testing.T) {
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "ReplicationController",
|
||||
Name: "example",
|
||||
},
|
||||
@@ -309,14 +309,14 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicationController(t *testing
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "ReplicaSet",
|
||||
Name: "example",
|
||||
},
|
||||
@@ -373,14 +373,14 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsReplicaSet(t *testing.T) {
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "StatefulSet",
|
||||
Name: "example",
|
||||
},
|
||||
@@ -437,14 +437,14 @@ func TestHPAAnalyzerWithExistingScaleTargetRefAsStatefulSet(t *testing.T) {
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
@@ -503,14 +503,14 @@ func TestHPAAnalyzerWithExistingScaleTargetRefWithoutSpecifyingResources(t *test
|
||||
|
||||
func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "other-namespace",
|
||||
@@ -531,3 +531,212 @@ func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "hpa",
|
||||
},
|
||||
},
|
||||
},
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=hpa",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
|
||||
func TestHPAAnalyzerStatusFieldAbleToScale(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
|
||||
Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
|
||||
{
|
||||
Type: "AbleToScale",
|
||||
Status: "False",
|
||||
Message: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func TestHPAAnalyzerStatusFieldScalingActive(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
|
||||
Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
|
||||
{
|
||||
Type: autoscalingv2.ScalingActive,
|
||||
Status: "False",
|
||||
Message: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
func TestHPAAnalyzerStatusFieldScalingLimited(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
|
||||
Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
|
||||
{
|
||||
Type: autoscalingv2.ScalingLimited,
|
||||
Status: "False",
|
||||
Message: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func TestHPAAnalyzerStatusField(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
Status: autoscalingv2.HorizontalPodAutoscalerStatus{
|
||||
Conditions: []autoscalingv2.HorizontalPodAutoscalerCondition{
|
||||
{
|
||||
Type: autoscalingv2.AbleToScale,
|
||||
Status: "True",
|
||||
Message: "recommended size matches current size",
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ScalingActive,
|
||||
Status: "True",
|
||||
Message: "the HPA was able to successfully calculate a replica count",
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ScalingLimited,
|
||||
Status: "True",
|
||||
Message: "the desired replica count is less than the minimum replica count",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
|
||||
}
|
||||
@@ -42,7 +42,9 @@ func (HTTPRouteAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.List(a.Context, routeList, &ctrl.ListOptions{}); err != nil {
|
||||
|
||||
labelSelector := util.LabelStrToSelector(a.LabelSelector)
|
||||
if err := client.List(a.Context, routeList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -201,3 +201,40 @@ func TestIngressAnalyzer(t *testing.T) {
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
validIgClassName := new(string)
|
||||
*validIgClassName = "valid-ingress-class"
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "ingress",
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=ingress",
|
||||
}
|
||||
|
||||
igAnalyzer := IngressAnalyzer{}
|
||||
results, err := igAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/Ingress1", results[0].Name)
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -118,3 +118,56 @@ func TestLogAnalyzer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
oldPattern := errorPattern
|
||||
errorPattern = regexp.MustCompile(`(fake logs)`)
|
||||
t.Cleanup(func() {
|
||||
errorPattern = oldPattern
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "log",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "test-container1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "test-container2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=log",
|
||||
}
|
||||
|
||||
logAnalyzer := LogAnalyzer{}
|
||||
results, err := logAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/Pod1/test-container1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (MutatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, erro
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
mutatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.Background(), v1.ListOptions{})
|
||||
mutatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -138,3 +138,78 @@ func TestMutatingWebhookAnalyzer(t *testing.T) {
|
||||
resultsLen := 3
|
||||
require.Equal(t, resultsLen, len(results))
|
||||
}
|
||||
|
||||
func TestMutatingWebhookAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "mutating-webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": "mutating-webhook",
|
||||
},
|
||||
},
|
||||
},
|
||||
&admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-mutating-webhook-config",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "mutating-webhook",
|
||||
},
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook1",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Name: "test-service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-mutating-webhook-config2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook2",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Name: "test-service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=mutating-webhook",
|
||||
}
|
||||
|
||||
mwAnalyzer := MutatingWebhookAnalyzer{}
|
||||
results, err := mwAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/webhook1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (NetworkPolicyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
|
||||
// get all network policies in the namespace
|
||||
policies, err := a.Client.GetClient().NetworkingV1().
|
||||
NetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
NetworkPolicies(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -220,3 +220,46 @@ func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
|
||||
assert.Equal(t, results[0].Kind, "NetworkPolicy")
|
||||
|
||||
}
|
||||
|
||||
func TestNetpolLabelSelectorFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "netpol",
|
||||
},
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PodSelector: metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "netpol",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=netpol",
|
||||
}
|
||||
|
||||
analyzer := NetworkPolicyAnalyzer{}
|
||||
results, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(results), 1)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
list, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().CoreV1().Nodes().List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -50,6 +50,9 @@ func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
break
|
||||
}
|
||||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
|
||||
// k3s `EtcdIsVoter`` should not be reported as an error
|
||||
case v1.NodeConditionType("EtcdIsVoter"):
|
||||
break
|
||||
default:
|
||||
if nodeCondition.Status != v1.ConditionFalse {
|
||||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
|
||||
|
||||
@@ -167,3 +167,51 @@ func TestNodeAnalyzer(t *testing.T) {
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "node",
|
||||
},
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=node",
|
||||
}
|
||||
|
||||
nAnalyzer := NodeAnalyzer{}
|
||||
results, err := nAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "Node1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -115,3 +115,94 @@ func TestPodDisruptionBudgetAnalyzer(t *testing.T) {
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "test/PDB3", results[0].Name)
|
||||
}
|
||||
|
||||
func TestPodDisruptionBudgetAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "PDB1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "pdb",
|
||||
},
|
||||
},
|
||||
// Status conditions are nil.
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: "DisruptionAllowed",
|
||||
Status: "False",
|
||||
Reason: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MaxUnavailable: &intstr.IntOrString{
|
||||
Type: 0,
|
||||
IntVal: 17,
|
||||
StrVal: "17",
|
||||
},
|
||||
MinAvailable: &intstr.IntOrString{
|
||||
Type: 0,
|
||||
IntVal: 7,
|
||||
StrVal: "7",
|
||||
},
|
||||
// MatchLabels specified.
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"label1": "test1",
|
||||
"label2": "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&policyv1.PodDisruptionBudget{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "PDB2",
|
||||
Namespace: "default",
|
||||
},
|
||||
// Status conditions are empty.
|
||||
Status: policyv1.PodDisruptionBudgetStatus{
|
||||
Conditions: []metav1.Condition{
|
||||
{
|
||||
Type: "DisruptionAllowed",
|
||||
Status: "False",
|
||||
Reason: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: policyv1.PodDisruptionBudgetSpec{
|
||||
MaxUnavailable: &intstr.IntOrString{
|
||||
Type: 0,
|
||||
IntVal: 17,
|
||||
StrVal: "17",
|
||||
},
|
||||
MinAvailable: &intstr.IntOrString{
|
||||
Type: 0,
|
||||
IntVal: 7,
|
||||
StrVal: "7",
|
||||
},
|
||||
// MatchLabels specified.
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"label1": "test1",
|
||||
"label2": "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=pdb",
|
||||
}
|
||||
|
||||
pdbAnalyzer := PdbAnalyzer{}
|
||||
results, err := pdbAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/PDB1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,9 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{
|
||||
LabelSelector: a.LabelSelector,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -228,3 +228,53 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPvcAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&appsv1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event1",
|
||||
Namespace: "default",
|
||||
},
|
||||
LastTimestamp: metav1.Time{
|
||||
Time: time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Reason: "ProvisioningFailed",
|
||||
Message: "PVC Event1 provisioning failed",
|
||||
},
|
||||
&appsv1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "PVC1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "pvc",
|
||||
},
|
||||
},
|
||||
Status: appsv1.PersistentVolumeClaimStatus{
|
||||
Phase: appsv1.ClaimPending,
|
||||
},
|
||||
},
|
||||
&appsv1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "PVC2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: appsv1.PersistentVolumeClaimStatus{
|
||||
Phase: appsv1.ClaimPending,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=pvc",
|
||||
}
|
||||
|
||||
pvcAnalyzer := PvcAnalyzer{}
|
||||
results, err := pvcAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/PVC1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -144,3 +144,58 @@ func TestReplicaSetAnalyzer(t *testing.T) {
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplicaSetAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&appsv1.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ReplicaSet1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "replicaset",
|
||||
},
|
||||
},
|
||||
Status: appsv1.ReplicaSetStatus{
|
||||
Replicas: 0,
|
||||
Conditions: []appsv1.ReplicaSetCondition{
|
||||
{
|
||||
// Should contribute to failures.
|
||||
Type: appsv1.ReplicaSetReplicaFailure,
|
||||
Reason: "FailedCreate",
|
||||
Message: "failed to create test replica set 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ReplicaSet2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: appsv1.ReplicaSetStatus{
|
||||
Replicas: 0,
|
||||
Conditions: []appsv1.ReplicaSetCondition{
|
||||
{
|
||||
// Should contribute to failures.
|
||||
Type: appsv1.ReplicaSetReplicaFailure,
|
||||
Reason: "FailedCreate",
|
||||
Message: "failed to create test replica set 1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=replicaset",
|
||||
}
|
||||
|
||||
rsAnalyzer := ReplicaSetAnalyzer{}
|
||||
results, err := rsAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/ReplicaSet1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
})
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -165,3 +165,106 @@ func TestServiceAnalyzer(t *testing.T) {
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientSet :=
|
||||
fake.NewSimpleClientset(
|
||||
&v1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Endpoint1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "service",
|
||||
"part-of": "test",
|
||||
},
|
||||
},
|
||||
// Endpoint with non-zero subsets.
|
||||
Subsets: []v1.EndpointSubset{
|
||||
{
|
||||
// These not ready end points will contribute to failures.
|
||||
NotReadyAddresses: []v1.EndpointAddress{
|
||||
{
|
||||
TargetRef: &v1.ObjectReference{
|
||||
Kind: "test-reference",
|
||||
Name: "reference1",
|
||||
},
|
||||
},
|
||||
{
|
||||
TargetRef: &v1.ObjectReference{
|
||||
Kind: "test-reference",
|
||||
Name: "reference2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// These not ready end points will contribute to failures.
|
||||
NotReadyAddresses: []v1.EndpointAddress{
|
||||
{
|
||||
TargetRef: &v1.ObjectReference{
|
||||
Kind: "test-reference",
|
||||
Name: "reference3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Service1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "service",
|
||||
},
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app1": "test-app1",
|
||||
"app2": "test-app2",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Service2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app1": "test-app1",
|
||||
"app2": "test-app2",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=service",
|
||||
}
|
||||
|
||||
sAnalyzer := ServiceAnalyzer{}
|
||||
results, err := sAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/Endpoint1", results[0].Name)
|
||||
|
||||
config = common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=service,part-of=test",
|
||||
}
|
||||
|
||||
sAnalyzer = ServiceAnalyzer{}
|
||||
results, err = sAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/Endpoint1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -188,3 +188,55 @@ func TestStatefulSetAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestStatefulSetAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientSet := fake.NewSimpleClientset(
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "statefulset",
|
||||
"part-of": "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example2",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=statefulset",
|
||||
}
|
||||
statefulSetAnalyzer := StatefulSetAnalyzer{}
|
||||
results, err := statefulSetAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 1, len(results))
|
||||
assert.Equal(t, "default/example1", results[0].Name)
|
||||
|
||||
config = common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=statefulset,part-of=test",
|
||||
}
|
||||
statefulSetAnalyzer = StatefulSetAnalyzer{}
|
||||
results, err = statefulSetAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, 1, len(results))
|
||||
assert.Equal(t, "default/example1", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (ValidatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, er
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
validatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.Background(), v1.ListOptions{})
|
||||
validatingWebhooks, err := a.Client.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -138,3 +138,80 @@ func TestValidatingWebhookAnalyzer(t *testing.T) {
|
||||
resultsLen := 3
|
||||
require.Equal(t, resultsLen, len(results))
|
||||
}
|
||||
|
||||
func TestValidatingWebhookAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientSet := fake.NewSimpleClientset(
|
||||
&admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-validating-webhook-config1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "validating-webhook",
|
||||
"part-of": "test",
|
||||
},
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
{
|
||||
// Failure: Pointing to an inactive receiver pod
|
||||
Name: "webhook1",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Name: "test-service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-validating-webhook-config2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
{
|
||||
// Failure: Pointing to an inactive receiver pod
|
||||
Name: "webhook1",
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
Service: &admissionregistrationv1.ServiceReference{
|
||||
Name: "test-service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=validating-webhook",
|
||||
}
|
||||
|
||||
vwAnalyzer := ValidatingWebhookAnalyzer{}
|
||||
results, err := vwAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.Equal(t, 1, len(results))
|
||||
|
||||
config = common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=validating-webhook,part-of=test",
|
||||
}
|
||||
|
||||
vwAnalyzer = ValidatingWebhookAnalyzer{}
|
||||
results, err = vwAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.Equal(t, 1, len(results))
|
||||
}
|
||||
|
||||
@@ -21,9 +21,10 @@ import (
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
keda "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
|
||||
kyverno "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2"
|
||||
regv1 "k8s.io/api/admissionregistration/v1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
autov1 "k8s.io/api/autoscaling/v1"
|
||||
autov2 "k8s.io/api/autoscaling/v2"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
@@ -38,6 +39,7 @@ type Analyzer struct {
|
||||
Client *kubernetes.Client
|
||||
Context context.Context
|
||||
Namespace string
|
||||
LabelSelector string
|
||||
AIClient ai.IAI
|
||||
PreAnalysis map[string]PreAnalysis
|
||||
Results []Result
|
||||
@@ -52,7 +54,7 @@ type PreAnalysis struct {
|
||||
PersistentVolumeClaim v1.PersistentVolumeClaim
|
||||
Endpoint v1.Endpoints
|
||||
Ingress networkv1.Ingress
|
||||
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
|
||||
HorizontalPodAutoscalers autov2.HorizontalPodAutoscaler
|
||||
PodDisruptionBudget policyv1.PodDisruptionBudget
|
||||
StatefulSet appsv1.StatefulSet
|
||||
NetworkPolicy networkv1.NetworkPolicy
|
||||
@@ -63,9 +65,11 @@ type PreAnalysis struct {
|
||||
Gateway gtwapi.Gateway
|
||||
HTTPRoute gtwapi.HTTPRoute
|
||||
// Integrations
|
||||
ScaledObject keda.ScaledObject
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
TrivyConfigAuditReport trivy.ConfigAuditReport
|
||||
ScaledObject keda.ScaledObject
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
TrivyConfigAuditReport trivy.ConfigAuditReport
|
||||
KyvernoPolicyReport kyverno.PolicyReport
|
||||
KyvernoClusterPolicyReport kyverno.ClusterPolicyReport
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/aws"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/kyverno"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/keda"
|
||||
@@ -52,6 +53,7 @@ var integrations = map[string]IIntegration{
|
||||
"prometheus": prometheus.NewPrometheus(),
|
||||
"aws": aws.NewAWS(),
|
||||
"keda": keda.NewKeda(),
|
||||
"kyverno": kyverno.NewKyverno(),
|
||||
}
|
||||
|
||||
func NewIntegration() *Integration {
|
||||
|
||||
162
pkg/integration/kyverno/analyzer.go
Normal file
162
pkg/integration/kyverno/analyzer.go
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 kyverno
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
|
||||
"github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2"
|
||||
)
|
||||
|
||||
// "github.com/kyverno/policy-reporter-kyverno-plugin/pkg/crd/api/policyreport/v1alpha2"
|
||||
|
||||
type KyvernoAnalyzer struct {
|
||||
policyReportAnalysis bool
|
||||
clusterReportAnalysis bool
|
||||
}
|
||||
|
||||
func (KyvernoAnalyzer) analyzePolicyReports(a common.Analyzer) ([]common.Result, error) {
|
||||
result := &v1alpha2.PolicyReportList{}
|
||||
client := a.Client.CtrlClient
|
||||
|
||||
err := v1alpha2.AddToScheme(client.Scheme())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find criticals and get CVE
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, report := range result.Items {
|
||||
|
||||
// For each pod there may be multiple vulnerabilities
|
||||
var failures []common.Failure
|
||||
for _, vuln := range report.Results {
|
||||
if vuln.Result == "fail" {
|
||||
// get the vulnerability ID
|
||||
// get the vulnerability description
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("policy failure: %s (message: %s)", vuln.Policy, vuln.Message),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", report.Namespace,
|
||||
report.Name)] = common.PreAnalysis{
|
||||
KyvernoPolicyReport: report,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "PolicyReport",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.KyvernoPolicyReport.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
|
||||
}
|
||||
|
||||
func (t KyvernoAnalyzer) analyzeClusterPolicyReports(a common.Analyzer) ([]common.Result, error) {
|
||||
result := &v1alpha2.ClusterPolicyReportList{}
|
||||
client := a.Client.CtrlClient
|
||||
|
||||
err := v1alpha2.AddToScheme(client.Scheme())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.List(a.Context, result, &ctrl.ListOptions{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find criticals and get CVE
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, report := range result.Items {
|
||||
|
||||
// For each pod there may be multiple vulnerabilities
|
||||
var failures []common.Failure
|
||||
for _, vuln := range report.Results {
|
||||
if vuln.Severity == "CRITICAL" {
|
||||
// get the vulnerability ID
|
||||
// get the vulnerability description
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("critical Vulnerability found ID: %s (learn more at: %s)", vuln.ID, vuln.Source),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", report.Namespace,
|
||||
report.Name)] = common.PreAnalysis{
|
||||
KyvernoClusterPolicyReport: report,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "ClusterPolicyReport",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.KyvernoClusterPolicyReport.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
|
||||
func (t KyvernoAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
if t.policyReportAnalysis {
|
||||
common := make([]common.Result, 0)
|
||||
vresult, err := t.analyzePolicyReports(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common = append(common, vresult...)
|
||||
return common, nil
|
||||
}
|
||||
if t.clusterReportAnalysis {
|
||||
common := make([]common.Result, 0)
|
||||
cresult, err := t.analyzeClusterPolicyReports(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
common = append(common, cresult...)
|
||||
return common, nil
|
||||
}
|
||||
return make([]common.Result, 0), nil
|
||||
}
|
||||
117
pkg/integration/kyverno/kyverno.go
Normal file
117
pkg/integration/kyverno/kyverno.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
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 kyverno
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Kyverno struct{}
|
||||
|
||||
func NewKyverno() *Kyverno {
|
||||
return &Kyverno{}
|
||||
}
|
||||
|
||||
func (k *Kyverno) GetAnalyzerName() []string {
|
||||
return []string{
|
||||
//from wgpolicyk8s.io/v1alpha2
|
||||
"PolicyReport",
|
||||
"ClusterPolicyReport",
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kyverno) OwnsAnalyzer(analyzer string) bool {
|
||||
|
||||
for _, a := range k.GetAnalyzerName() {
|
||||
if analyzer == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Kyverno) isDeployed() bool {
|
||||
// check if wgpolicyk8s apigroup is available as a marker if new policy resource available is installed on the cluster
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
kubeconfig := viper.GetString("kubeconfig")
|
||||
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
|
||||
if err != nil {
|
||||
// TODO: better error handling
|
||||
color.Red("Error initialising kubernetes client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
groups, _, err := client.Client.Discovery().ServerGroupsAndResources()
|
||||
if err != nil {
|
||||
// TODO: better error handling
|
||||
color.Red("Error initialising discovery client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.Name == "kyverno.io" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Kyverno) isFilterActive() bool {
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
for _, filter := range k.GetAnalyzerName() {
|
||||
for _, af := range activeFilters {
|
||||
if af == filter {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Kyverno) IsActivate() bool {
|
||||
if k.isFilterActive() && k.isDeployed() {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kyverno) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
|
||||
|
||||
(*mergedMap)["PolicyReport"] = &KyvernoAnalyzer{
|
||||
policyReportAnalysis: true,
|
||||
}
|
||||
(*mergedMap)["ClusterPolicyReport"] = &KyvernoAnalyzer{
|
||||
clusterReportAnalysis: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kyverno) Deploy(namespace string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Kyverno) UnDeploy(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Kyverno) GetNamespace() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"context"
|
||||
json "encoding/json"
|
||||
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
|
||||
)
|
||||
|
||||
@@ -25,11 +24,13 @@ func (h *handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (
|
||||
i.Language,
|
||||
i.Filters,
|
||||
i.Namespace,
|
||||
i.LabelSelector,
|
||||
i.Nocache,
|
||||
i.Explain,
|
||||
int(i.MaxConcurrency),
|
||||
false, // Kubernetes Doc disabled in server mode
|
||||
false, // Interactive mode disabled in server mode
|
||||
false, // Kubernetes Doc disabled in server mode
|
||||
false, // Interactive mode disabled in server mode
|
||||
[]string{}, //TODO: add custom http headers in server mode
|
||||
)
|
||||
config.Context = ctx // Replace context for correct timeouts.
|
||||
if err != nil {
|
||||
@@ -37,6 +38,9 @@ func (h *handler) Analyze(ctx context.Context, i *schemav1.AnalyzeRequest) (
|
||||
}
|
||||
defer config.Close()
|
||||
|
||||
if config.CustomAnalyzersAreAvailable() {
|
||||
config.RunCustomAnalysis()
|
||||
}
|
||||
config.RunAnalysis()
|
||||
|
||||
if i.Explain {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"context"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/custom"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
@@ -27,6 +28,38 @@ func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if i.CustomAnalyzers != nil {
|
||||
// We need to add the custom analyzers to the viper config and save them
|
||||
var customAnalyzers = make([]custom.CustomAnalyzer, 0)
|
||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||
return resp, err
|
||||
} else {
|
||||
// If there are analyzers are already in the config we will append the ones with new names
|
||||
for _, ca := range i.CustomAnalyzers {
|
||||
exists := false
|
||||
for _, c := range customAnalyzers {
|
||||
if c.Name == ca.Name {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
customAnalyzers = append(customAnalyzers, custom.CustomAnalyzer{
|
||||
Name: ca.Name,
|
||||
Connection: custom.Connection{
|
||||
Url: ca.Connection.Url,
|
||||
Port: ca.Connection.Port,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// save the config
|
||||
viper.Set("custom_analyzers", customAnalyzers)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if i.Cache != nil {
|
||||
var err error
|
||||
var remoteCache cache.CacheProvider
|
||||
|
||||
@@ -21,10 +21,13 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -261,3 +264,50 @@ func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client,
|
||||
}
|
||||
return latestEvent, nil
|
||||
}
|
||||
|
||||
// NewHeaders parses a slice of strings in the format "key:value" into []http.Header
|
||||
// It handles headers with the same key by appending values
|
||||
func NewHeaders(customHeaders []string) []http.Header {
|
||||
headers := make(map[string][]string)
|
||||
|
||||
for _, header := range customHeaders {
|
||||
vals := strings.SplitN(header, ":", 2)
|
||||
if len(vals) != 2 {
|
||||
//TODO: Handle error instead of ignoring it
|
||||
continue
|
||||
}
|
||||
key := strings.TrimSpace(vals[0])
|
||||
value := strings.TrimSpace(vals[1])
|
||||
|
||||
if _, ok := headers[key]; !ok {
|
||||
headers[key] = []string{}
|
||||
}
|
||||
headers[key] = append(headers[key], value)
|
||||
}
|
||||
|
||||
// Convert map to []http.Header format
|
||||
var result []http.Header
|
||||
for key, values := range headers {
|
||||
header := make(http.Header)
|
||||
for _, value := range values {
|
||||
header.Add(key, value)
|
||||
}
|
||||
result = append(result, header)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func LabelStrToSelector(labelStr string) labels.Selector {
|
||||
if labelStr == "" {
|
||||
return nil
|
||||
}
|
||||
labelSelectorMap := make(map[string]string)
|
||||
for _, s := range strings.Split(labelStr, ",") {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
labelSelectorMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
return labels.SelectorFromSet(labels.Set(labelSelectorMap))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user