mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-18 19:17:25 +00:00
Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
3f80bbaa1b | ||
|
|
9bace02a67 | ||
|
|
7b1b63322e | ||
|
|
f963e4e0f4 | ||
|
|
2382de4c6f | ||
|
|
55ae7c3298 | ||
|
|
aeae2ba765 | ||
|
|
602d111d85 | ||
|
|
c3f164eb2b | ||
|
|
92dd1bd8b0 | ||
|
|
4867d39c66 | ||
|
|
c834c09996 | ||
|
|
038e52e044 | ||
|
|
f9d734ee53 | ||
|
|
c101e8a3ea | ||
|
|
63b63f7664 | ||
|
|
0fe984966a | ||
|
|
3a893184af | ||
|
|
6652fbe7cb | ||
|
|
728555c0ef | ||
|
|
bdd470f9ca | ||
|
|
fad00eac49 | ||
|
|
3452c0def6 | ||
|
|
ffd017fbd7 | ||
|
|
e261c09889 | ||
|
|
cc890dfa46 | ||
|
|
047afd46d6 | ||
|
|
eda52312ae | ||
|
|
882c6f5225 | ||
|
|
fe53907c44 | ||
|
|
63f7fcfef7 | ||
|
|
2c7c74472c | ||
|
|
3c4823127c | ||
|
|
c3a884f0c4 | ||
|
|
e74fc0838f | ||
|
|
f30c9f5554 | ||
|
|
36ccc62846 | ||
|
|
a809a455f5 | ||
|
|
12fa5aef4d | ||
|
|
75c2addf66 | ||
|
|
9b797d7e8b | ||
|
|
820cd2e16c | ||
|
|
43953ffa34 | ||
|
|
bd695d0987 | ||
|
|
b12c006c63 | ||
|
|
e0af76f3c9 | ||
|
|
e894e778e9 | ||
|
|
5cfe3325cb | ||
|
|
ea8183ce84 | ||
|
|
24cff90a0c | ||
|
|
bf6f642c28 | ||
|
|
6279f358ca | ||
|
|
b2ab94375e | ||
|
|
9a73d1923f | ||
|
|
85a76a3be0 | ||
|
|
aa276a5379 | ||
|
|
eb7687a089 | ||
|
|
c162cc22ee | ||
|
|
1ae4e75196 | ||
|
|
693b23f1fc | ||
|
|
e6085d4191 | ||
|
|
3eaf776249 | ||
|
|
ccb692c1fd | ||
|
|
9e0263778f | ||
|
|
9dfcce842e | ||
|
|
6df0169491 | ||
|
|
007b4bb8ec | ||
|
|
6b38a56afb | ||
|
|
19ae31b5dd | ||
|
|
6a46a26789 | ||
|
|
e05a902d90 | ||
|
|
a3896f4518 | ||
|
|
4262c9292c | ||
|
|
94cdce44b4 | ||
|
|
10c00ba9fe | ||
|
|
c872e495ad | ||
|
|
bd2e06bae7 | ||
|
|
5db4bc28a7 | ||
|
|
8f8f5c6df7 | ||
|
|
3c1c055ac7 | ||
|
|
ebfbba98ca | ||
|
|
47463d4412 | ||
|
|
fe81d16f75 | ||
|
|
a1d0d0a180 | ||
|
|
f60467cd4d | ||
|
|
20892b48d0 |
20
.github/workflows/build_container.yaml
vendored
20
.github/workflows/build_container.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
- "**.md"
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.21"
|
||||
GO_VERSION: "~1.22"
|
||||
IMAGE_NAME: "k8sgpt"
|
||||
defaults:
|
||||
run:
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: Extract branch name
|
||||
id: extract_branch
|
||||
@@ -70,14 +70,14 @@ jobs:
|
||||
RELEASE_REGISTRY: "localhost:5000/k8sgpt"
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@af5a7ed5ba88268d5278f7203fb52cd833f66d6e # 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4
|
||||
with:
|
||||
name: ${{ env.IMAGE_NAME }}-image.tar
|
||||
path: /tmp/${{ env.IMAGE_NAME }}-image.tar
|
||||
@@ -115,10 +115,10 @@ jobs:
|
||||
contents: read # Needed for checking out the repository
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: ${{ github.actor }}
|
||||
@@ -126,10 +126,10 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@af5a7ed5ba88268d5278f7203fb52cd833f66d6e # v5
|
||||
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
|
||||
with:
|
||||
context: .
|
||||
file: ./container/Dockerfile
|
||||
|
||||
4
.github/workflows/golangci_lint.yaml
vendored
4
.github/workflows/golangci_lint.yaml
vendored
@@ -9,10 +9,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: reviewdog/action-golangci-lint@00311c26a97213f93f2fd3a3524d66762e956ae0 # v2
|
||||
uses: reviewdog/action-golangci-lint@7708105983c614f7a2725e2172908b7709d1c3e4 # v2
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: github-pr-check
|
||||
|
||||
26
.github/workflows/release.yaml
vendored
26
.github/workflows/release.yaml
vendored
@@ -23,9 +23,9 @@ jobs:
|
||||
# Release-please creates a PR that tracks all changes
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- uses: google-github-actions/release-please-action@cc61a07e2da466bebbc19b3a7dd01d6aecb20d1e # v4.0.2
|
||||
- uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4.1.1
|
||||
id: release
|
||||
with:
|
||||
command: manifest
|
||||
@@ -41,17 +41,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: '1.22'
|
||||
- name: Download Syft
|
||||
uses: anchore/sbom-action/download-syft@9fece9e20048ca9590af301449208b2b8861333b # v0.15.9
|
||||
uses: anchore/sbom-action/download-syft@95b086ac308035dc0850b3853be5b7ab108236a8 # v0.16.1
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # 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'
|
||||
@@ -74,23 +76,23 @@ jobs:
|
||||
IMAGE_NAME: k8sgpt
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c # v3
|
||||
uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3
|
||||
uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3
|
||||
with:
|
||||
registry: "ghcr.io"
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@af5a7ed5ba88268d5278f7203fb52cd833f66d6e # 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@9fece9e20048ca9590af301449208b2b8861333b # v0.15.9
|
||||
uses: anchore/sbom-action@95b086ac308035dc0850b3853be5b7ab108236a8 # v0.16.1
|
||||
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@e9fabac35e210fea40ca5b14c0da95a099eff26f # v5.4.0
|
||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
13
.github/workflows/test.yaml
vendored
13
.github/workflows/test.yaml
vendored
@@ -9,19 +9,22 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.21"
|
||||
GO_VERSION: "~1.22"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run test
|
||||
run: go test ./...
|
||||
run: go test ./... -coverprofile=coverage.txt
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@ab904c41d6ece82784817410c45d8b8c02684457 # v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
version: 2
|
||||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
@@ -14,12 +15,14 @@ builds:
|
||||
- windows
|
||||
- darwin
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}}
|
||||
- -s -w -X main.commit={{.ShortCommit}}
|
||||
- -s -w -X main.Date={{.CommitDate}}
|
||||
- -s -w
|
||||
- -X main.version={{.Version}}
|
||||
- -X main.commit={{.ShortCommit}}
|
||||
- -X main.Date={{.CommitDate}}
|
||||
|
||||
nfpms:
|
||||
- file_name_template: '{{ .ProjectName }}_{{ .Arch }}'
|
||||
- file_name_template: "{{ .ProjectName }}_{{ .Arch }}"
|
||||
maintainer: "K8sGPT Maintainers <contact@k8sgpt.ai>"
|
||||
homepage: https://k8sgpt.ai
|
||||
description: >-
|
||||
K8sGPT is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english. It has SRE experience codified into it’s analyzers and helps to pull out the most relevant information to enrich it with AI.
|
||||
@@ -32,7 +35,7 @@ nfpms:
|
||||
section: utils
|
||||
contents:
|
||||
- src: ./LICENSE
|
||||
dst: /usr/share/doc/nfpm/copyright
|
||||
dst: /usr/share/doc/k8sgpt/copyright
|
||||
file_info:
|
||||
mode: 0644
|
||||
|
||||
@@ -51,8 +54,8 @@ archives:
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
brews:
|
||||
- name: k8sgpt
|
||||
@@ -62,14 +65,12 @@ brews:
|
||||
name: homebrew-k8sgpt
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
|
||||
changelog:
|
||||
skip: true
|
||||
|
||||
# skip: true
|
||||
# The lines beneath this are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
|
||||
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.28"}
|
||||
{".":"0.3.39"}
|
||||
212
CHANGELOG.md
212
CHANGELOG.md
@@ -1,5 +1,217 @@
|
||||
# Changelog
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update reviewdog/action-golangci-lint digest to 7708105 ([#1157](https://github.com/k8sgpt-ai/k8sgpt/issues/1157)) ([7b1b633](https://github.com/k8sgpt-ai/k8sgpt/commit/7b1b63322ec7b0c0864682bc23be6e70c0ed7ec7))
|
||||
* updated the goreleaser action ([#1160](https://github.com/k8sgpt-ai/k8sgpt/issues/1160)) ([9bace02](https://github.com/k8sgpt-ai/k8sgpt/commit/9bace02a6702a8af0e6511b51ffc38378e14d3cb))
|
||||
|
||||
## [0.3.36](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.35...v0.3.36) (2024-06-17)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update docker/login-action digest to 0d4c9c5 ([#1141](https://github.com/k8sgpt-ai/k8sgpt/issues/1141)) ([602d111](https://github.com/k8sgpt-ai/k8sgpt/commit/602d111d8568d38cda744d2b179ee2d3eb59ba02))
|
||||
* **deps:** update goreleaser/goreleaser-action digest to 5742e2a ([#1153](https://github.com/k8sgpt-ai/k8sgpt/issues/1153)) ([55ae7c3](https://github.com/k8sgpt-ai/k8sgpt/commit/55ae7c32986100d4b0bab6dcaf7a52ac7b37aa5f))
|
||||
* fixed the goreleaser file ([#1158](https://github.com/k8sgpt-ai/k8sgpt/issues/1158)) ([2382de4](https://github.com/k8sgpt-ai/k8sgpt/commit/2382de4c6f82de535b67c2752d7c502d0a8b2b66))
|
||||
* update goreleaser ldflags ([#1154](https://github.com/k8sgpt-ai/k8sgpt/issues/1154)) ([aeae2ba](https://github.com/k8sgpt-ai/k8sgpt/commit/aeae2ba765c7db6e4953b5a93c54617f1dd85efa))
|
||||
|
||||
## [0.3.35](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.34...v0.3.35) (2024-06-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add spec.template.spec.securityContext ([#1109](https://github.com/k8sgpt-ai/k8sgpt/issues/1109)) ([92dd1bd](https://github.com/k8sgpt-ai/k8sgpt/commit/92dd1bd8b08c5173f72a6c333f626c63aa05a1d3))
|
||||
* support openai organization Id ([#1133](https://github.com/k8sgpt-ai/k8sgpt/issues/1133)) ([4867d39](https://github.com/k8sgpt-ai/k8sgpt/commit/4867d39c66a6c16906cd769a2055dea9f66f1ccb))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* updated goreleaser config ([#1149](https://github.com/k8sgpt-ai/k8sgpt/issues/1149)) ([c834c09](https://github.com/k8sgpt-ai/k8sgpt/commit/c834c099969f3e888f49f73fba6794387063a6fc))
|
||||
|
||||
## [0.3.34](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.33...v0.3.34) (2024-06-14)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update google-github-actions/release-please-action action to v4.1.1 ([#1143](https://github.com/k8sgpt-ai/k8sgpt/issues/1143)) ([63b63f7](https://github.com/k8sgpt-ai/k8sgpt/commit/63b63f7664277042188351073f269569bfec65bf))
|
||||
* **deps:** update goreleaser/goreleaser-action digest to 5742e2a ([#1142](https://github.com/k8sgpt-ai/k8sgpt/issues/1142)) ([c101e8a](https://github.com/k8sgpt-ai/k8sgpt/commit/c101e8a3ea6d911d00ca2a51986edc5425a1042a))
|
||||
|
||||
## [0.3.33](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.32...v0.3.33) (2024-06-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* bump golang version to 1.22 ([#1117](https://github.com/k8sgpt-ai/k8sgpt/issues/1117)) ([6652fbe](https://github.com/k8sgpt-ai/k8sgpt/commit/6652fbe7cb6e581497e1d086e13397ff9e5b11be))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* advisory k8sgpt ghsa 85rg 8m6h 825p ([#1139](https://github.com/k8sgpt-ai/k8sgpt/issues/1139)) ([728555c](https://github.com/k8sgpt-ai/k8sgpt/commit/728555c0effbf7a56221d625bcbbf62f74d14359))
|
||||
* **deps:** typo in prometheus.go ([fad00ea](https://github.com/k8sgpt-ai/k8sgpt/commit/fad00eac4925351c4dc6fd6dd347fe2968f0b7a5))
|
||||
* **deps:** typo in prometheus.go ([#1137](https://github.com/k8sgpt-ai/k8sgpt/issues/1137)) ([fad00ea](https://github.com/k8sgpt-ai/k8sgpt/commit/fad00eac4925351c4dc6fd6dd347fe2968f0b7a5))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.53.21 ([#1106](https://github.com/k8sgpt-ai/k8sgpt/issues/1106)) ([bdd470f](https://github.com/k8sgpt-ai/k8sgpt/commit/bdd470f9cae917f965badd22da7def4a7d64d2ae))
|
||||
* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.6.0 [security] ([#1138](https://github.com/k8sgpt-ai/k8sgpt/issues/1138)) ([3a89318](https://github.com/k8sgpt-ai/k8sgpt/commit/3a893184af50f8c822ac06ce0e20818eaec587b1))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update actions/setup-go digest to cdcb360 ([#1096](https://github.com/k8sgpt-ai/k8sgpt/issues/1096)) ([3452c0d](https://github.com/k8sgpt-ai/k8sgpt/commit/3452c0def68fd5352d2d09201f813f657245bd9f))
|
||||
|
||||
## [0.3.32](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.31...v0.3.32) (2024-05-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove shorthand flag for topp option in add command ([#1115](https://github.com/k8sgpt-ai/k8sgpt/issues/1115)) ([e261c09](https://github.com/k8sgpt-ai/k8sgpt/commit/e261c09889359d5870acb9720ff033440f835f8f))
|
||||
|
||||
## [0.3.31](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.30...v0.3.31) (2024-05-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* implement Top-K sampling for improved user control ([#1110](https://github.com/k8sgpt-ai/k8sgpt/issues/1110)) ([eda5231](https://github.com/k8sgpt-ai/k8sgpt/commit/eda52312aef8113debbd770b8354c3a3cb1cc681))
|
||||
* oci genai ([#1102](https://github.com/k8sgpt-ai/k8sgpt/issues/1102)) ([047afd4](https://github.com/k8sgpt-ai/k8sgpt/commit/047afd46d62d1bd1da1435550cbaf9daaca53aee))
|
||||
* support AWS_PROFILE ([#1114](https://github.com/k8sgpt-ai/k8sgpt/issues/1114)) ([882c6f5](https://github.com/k8sgpt-ai/k8sgpt/commit/882c6f52252000da436e4fed9fd184b263f5a017))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to 0849a56 ([#1080](https://github.com/k8sgpt-ai/k8sgpt/issues/1080)) ([e894e77](https://github.com/k8sgpt-ai/k8sgpt/commit/e894e778e91d070448cd4a3f46dfc98dd588c9ed))
|
||||
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 to v2.19.1-20240406062209-1cc152efbf5c.1 ([#1070](https://github.com/k8sgpt-ai/k8sgpt/issues/1070)) ([24cff90](https://github.com/k8sgpt-ai/k8sgpt/commit/24cff90a0ca7488e48c94d13678529617c749aab))
|
||||
* **deps:** update module buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go to v1.3.0-20240406062209-1cc152efbf5c.3 ([#1086](https://github.com/k8sgpt-ai/k8sgpt/issues/1086)) ([820cd2e](https://github.com/k8sgpt-ai/k8sgpt/commit/820cd2e16cbca2c89b56a4d2a69f95f3f5cd6c6b))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.51.32 ([#1083](https://github.com/k8sgpt-ai/k8sgpt/issues/1083)) ([75c2add](https://github.com/k8sgpt-ai/k8sgpt/commit/75c2addf66a54df57d0c0ac17f0b359f7612e446))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.52.3 ([#1094](https://github.com/k8sgpt-ai/k8sgpt/issues/1094)) ([3c48231](https://github.com/k8sgpt-ai/k8sgpt/commit/3c4823127ca04d1d280da6d932e951e6c3f71536))
|
||||
* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/azidentity to v1.5.2 ([#1084](https://github.com/k8sgpt-ai/k8sgpt/issues/1084)) ([bd695d0](https://github.com/k8sgpt-ai/k8sgpt/commit/bd695d0987e8ec12b44512c46bc5f2e5116076bd))
|
||||
* **deps:** update module github.com/azure/azure-sdk-for-go/sdk/storage/azblob to v1.3.2 ([#1085](https://github.com/k8sgpt-ai/k8sgpt/issues/1085)) ([43953ff](https://github.com/k8sgpt-ai/k8sgpt/commit/43953ffa3412ae97b6d54ed14b94955d1b73feba))
|
||||
* **deps:** update module github.com/cohere-ai/cohere-go/v2 to v2.7.3 ([#1087](https://github.com/k8sgpt-ai/k8sgpt/issues/1087)) ([36ccc62](https://github.com/k8sgpt-ai/k8sgpt/commit/36ccc628462ad102712fca115b56f521b2b33b38))
|
||||
* **deps:** update module github.com/google/generative-ai-go to v0.11.0 ([#1089](https://github.com/k8sgpt-ai/k8sgpt/issues/1089)) ([f30c9f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f30c9f555449bb90bf8242b88b8fae936cb57938))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.23.0 ([#1091](https://github.com/k8sgpt-ai/k8sgpt/issues/1091)) ([e74fc08](https://github.com/k8sgpt-ai/k8sgpt/commit/e74fc0838feac5a019a340f7c5ad1c9ae49913fa))
|
||||
* **deps:** update module golang.org/x/net to v0.25.0 ([#1092](https://github.com/k8sgpt-ai/k8sgpt/issues/1092)) ([fe53907](https://github.com/k8sgpt-ai/k8sgpt/commit/fe53907c44e9cd56b6747f52ae3402bc6ae2bd49))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** pin codecov/codecov-action action to ab904c4 ([#1031](https://github.com/k8sgpt-ai/k8sgpt/issues/1031)) ([e0af76f](https://github.com/k8sgpt-ai/k8sgpt/commit/e0af76f3c9c0120dbc4d9373d69a262e1ec2b7f2))
|
||||
* **deps:** update actions/checkout digest to 0ad4b8f ([#1078](https://github.com/k8sgpt-ai/k8sgpt/issues/1078)) ([ea8183c](https://github.com/k8sgpt-ai/k8sgpt/commit/ea8183ce848ba58f91cfa68755d6f5b9cf695d36))
|
||||
* **deps:** update actions/upload-artifact digest to 6546280 ([#1079](https://github.com/k8sgpt-ai/k8sgpt/issues/1079)) ([9b797d7](https://github.com/k8sgpt-ai/k8sgpt/commit/9b797d7e8b4f704dae12acaa7778b6b65e2c36ac))
|
||||
* **deps:** update amannn/action-semantic-pull-request action to v5.5.2 ([#1088](https://github.com/k8sgpt-ai/k8sgpt/issues/1088)) ([a809a45](https://github.com/k8sgpt-ai/k8sgpt/commit/a809a455f55d1af104ebc0540007aa678581dd21))
|
||||
* **deps:** update anchore/sbom-action action to v0.15.11 ([#1082](https://github.com/k8sgpt-ai/k8sgpt/issues/1082)) ([12fa5ae](https://github.com/k8sgpt-ai/k8sgpt/commit/12fa5aef4dada597d7059e5717ec7bee3b38c122))
|
||||
* **deps:** update docker/build-push-action digest to 2cdde99 ([#1032](https://github.com/k8sgpt-ai/k8sgpt/issues/1032)) ([b12c006](https://github.com/k8sgpt-ai/k8sgpt/commit/b12c006c6304165269b90d770048b851e1aa1d1f))
|
||||
* **deps:** update google-github-actions/release-please-action action to v4.1.0 ([#1045](https://github.com/k8sgpt-ai/k8sgpt/issues/1045)) ([bf6f642](https://github.com/k8sgpt-ai/k8sgpt/commit/bf6f642c280f640f2c9020b325e52670ced2cf50))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add logAnalyzer in README.md ([#1081](https://github.com/k8sgpt-ai/k8sgpt/issues/1081)) ([5cfe332](https://github.com/k8sgpt-ai/k8sgpt/commit/5cfe3325cb556cfb9d0532ae26727441c5177015))
|
||||
|
||||
## [0.3.30](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.29...v0.3.30) (2024-04-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add keda integration ([#1058](https://github.com/k8sgpt-ai/k8sgpt/issues/1058)) ([9a73d19](https://github.com/k8sgpt-ai/k8sgpt/commit/9a73d1923f146aa1343465d89225e64bcb8e0112))
|
||||
* add minio support ([#1048](https://github.com/k8sgpt-ai/k8sgpt/issues/1048)) ([e6085d4](https://github.com/k8sgpt-ai/k8sgpt/commit/e6085d4191a1695e295f4f6a2ac7219b67a37225))
|
||||
* add Resource Kind in output ([#1069](https://github.com/k8sgpt-ai/k8sgpt/issues/1069)) ([aa276a5](https://github.com/k8sgpt-ai/k8sgpt/commit/aa276a5379b3d24a8e7a1f8b1193832df5a46220))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to 4693a02 ([#1037](https://github.com/k8sgpt-ai/k8sgpt/issues/1037)) ([94cdce4](https://github.com/k8sgpt-ai/k8sgpt/commit/94cdce44b49e0bb85e8b541688b2206e7c1dc33d))
|
||||
* **deps:** update module cloud.google.com/go/storage to v1.39.1 ([#1029](https://github.com/k8sgpt-ai/k8sgpt/issues/1029)) ([a3896f4](https://github.com/k8sgpt-ai/k8sgpt/commit/a3896f4518ec6666a43de22a24a18f2b93c58073))
|
||||
* **deps:** update module cloud.google.com/go/storage to v1.40.0 ([#1054](https://github.com/k8sgpt-ai/k8sgpt/issues/1054)) ([6df0169](https://github.com/k8sgpt-ai/k8sgpt/commit/6df01694916504cc4af3795361a4285098e2de85))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.51.14 ([#1051](https://github.com/k8sgpt-ai/k8sgpt/issues/1051)) ([007b4bb](https://github.com/k8sgpt-ai/k8sgpt/commit/007b4bb8ec4b36705f76fd2f5d96464c75915573))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.51.21 ([#1056](https://github.com/k8sgpt-ai/k8sgpt/issues/1056)) ([ccb692c](https://github.com/k8sgpt-ai/k8sgpt/commit/ccb692c1fdc5496d9d5810dfe41dbf1bdeb68d00))
|
||||
* **deps:** update module github.com/aws/aws-sdk-go to v1.51.8 ([#1046](https://github.com/k8sgpt-ai/k8sgpt/issues/1046)) ([19ae31b](https://github.com/k8sgpt-ai/k8sgpt/commit/19ae31b5dd5c54413025cee8081d112223e38400))
|
||||
* **deps:** update module github.com/google/generative-ai-go to v0.10.0 ([#1047](https://github.com/k8sgpt-ai/k8sgpt/issues/1047)) ([6b38a56](https://github.com/k8sgpt-ai/k8sgpt/commit/6b38a56afbdaa8e0d8f025088a52d3022673ef9d))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.20.4 ([#1039](https://github.com/k8sgpt-ai/k8sgpt/issues/1039)) ([6a46a26](https://github.com/k8sgpt-ai/k8sgpt/commit/6a46a26789f730d298cf49a706421f36bc8523b1))
|
||||
* **deps:** update module golang.org/x/net to v0.23.0 [security] ([#1071](https://github.com/k8sgpt-ai/k8sgpt/issues/1071)) ([693b23f](https://github.com/k8sgpt-ai/k8sgpt/commit/693b23f1fc33659a3c4f52fc4d9c23348b22bfb1))
|
||||
* invalid ParentObj in output ([#1068](https://github.com/k8sgpt-ai/k8sgpt/issues/1068)) ([b2ab943](https://github.com/k8sgpt-ai/k8sgpt/commit/b2ab94375e4233cdfa9762877995445c313bb962))
|
||||
* remove show password in auth list ([#1061](https://github.com/k8sgpt-ai/k8sgpt/issues/1061)) ([9e02637](https://github.com/k8sgpt-ai/k8sgpt/commit/9e0263778f6dbc179184fa9d86f07d808283d63e))
|
||||
* set topP from config ([#1053](https://github.com/k8sgpt-ai/k8sgpt/issues/1053)) ([c162cc2](https://github.com/k8sgpt-ai/k8sgpt/commit/c162cc22ee468070e0602d3fd684b022fa585c4f))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update anchore/sbom-action action to v0.15.10 ([#1044](https://github.com/k8sgpt-ai/k8sgpt/issues/1044)) ([e05a902](https://github.com/k8sgpt-ai/k8sgpt/commit/e05a902d904fc0b63998ae290f15e79d330317fb))
|
||||
* **deps:** update cohere client implementation to v2 ([#1062](https://github.com/k8sgpt-ai/k8sgpt/issues/1062)) ([eb7687a](https://github.com/k8sgpt-ai/k8sgpt/commit/eb7687a08917ad4048c6f00c17bb45591a935a3a))
|
||||
* **deps:** update docker/login-action digest to e92390c ([#1033](https://github.com/k8sgpt-ai/k8sgpt/issues/1033)) ([c872e49](https://github.com/k8sgpt-ai/k8sgpt/commit/c872e495ad6f787cf566a5b2f295deb3f08aba15))
|
||||
* **deps:** update docker/setup-buildx-action digest to 2b51285 ([#1036](https://github.com/k8sgpt-ai/k8sgpt/issues/1036)) ([10c00ba](https://github.com/k8sgpt-ai/k8sgpt/commit/10c00ba9fe61a3ee1dc90d87dd7997da276905b4))
|
||||
* **deps:** update docker/setup-buildx-action digest to d70bba7 ([#1066](https://github.com/k8sgpt-ai/k8sgpt/issues/1066)) ([3eaf776](https://github.com/k8sgpt-ai/k8sgpt/commit/3eaf776249719a0a13909d24e6b48deb6bf818b6))
|
||||
* update license file path to avoid conflicting installations ([#878](https://github.com/k8sgpt-ai/k8sgpt/issues/878)) ([#1073](https://github.com/k8sgpt-ai/k8sgpt/issues/1073)) ([85a76a3](https://github.com/k8sgpt-ai/k8sgpt/commit/85a76a3be06df0ff713192d1f08fd01d1e8f219b))
|
||||
* update renovate config and bundle deps in groups ([#1026](https://github.com/k8sgpt-ai/k8sgpt/issues/1026)) ([bd2e06b](https://github.com/k8sgpt-ai/k8sgpt/commit/bd2e06bae72528c5af1b4f44674d624d474d40dc))
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* replace util.SliceContainsString with slices.Contains & make fmt ([#1041](https://github.com/k8sgpt-ai/k8sgpt/issues/1041)) ([1ae4e75](https://github.com/k8sgpt-ai/k8sgpt/commit/1ae4e751967850e8146f8f3fa04c0dd302ef15bf))
|
||||
|
||||
## [0.3.29](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.28...v0.3.29) (2024-03-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* codecov ([#1023](https://github.com/k8sgpt-ai/k8sgpt/issues/1023)) ([fe81d16](https://github.com/k8sgpt-ai/k8sgpt/commit/fe81d16f756e5ea9db909e42e6caf1e17e040f86))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* allows an environmental override of the default AWS region and… ([#1025](https://github.com/k8sgpt-ai/k8sgpt/issues/1025)) ([8f8f5c6](https://github.com/k8sgpt-ai/k8sgpt/commit/8f8f5c6df7fbcd08ee48d91a4f2e011a3e69e4ac))
|
||||
|
||||
## [0.3.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.27...v0.3.28) (2024-03-14)
|
||||
|
||||
|
||||
|
||||
46
README.md
46
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.28/k8sgpt_386.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/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.28/k8sgpt_amd64.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/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.28/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/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.28/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/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.28/k8sgpt_386.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/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.28/k8sgpt_amd64.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.39/k8sgpt_amd64.apk
|
||||
apk add k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->x
|
||||
@@ -167,6 +173,7 @@ you will be able to write your own analyzers.
|
||||
- [x] gatewayClass
|
||||
- [x] gateway
|
||||
- [x] httproute
|
||||
- [x] logAnalyzer
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -292,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
|
||||
@@ -301,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
|
||||
@@ -315,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/).
|
||||
@@ -379,6 +394,7 @@ Note: **Anonymization does not currently apply to events.**
|
||||
- RepicaSet
|
||||
- PersistentVolumeClaim
|
||||
- Pod
|
||||
- Log
|
||||
- **_*Events_**
|
||||
|
||||
***Note**:
|
||||
@@ -423,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_
|
||||
@@ -431,16 +447,18 @@ _Adding a remote cache_
|
||||
* AWS S3
|
||||
* _As a prerequisite `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are required as environmental variables._
|
||||
* Configuration, ``` k8sgpt cache add s3 --region <aws region> --bucket <name> ```
|
||||
* Minio Configuration with HTTP endpoint ``` k8sgpt cache add s3 --bucket <name> --endpoint <http://localhost:9000>```
|
||||
* Minio Configuration with HTTPs endpoint, skipping TLS verification ``` k8sgpt cache add s3 --bucket <name> --endpoint <https://localhost:9000> --insecure```
|
||||
* K8sGPT will create the bucket if it does not exist
|
||||
* Azure Storage
|
||||
* 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_
|
||||
```
|
||||
|
||||
@@ -21,6 +21,10 @@ spec:
|
||||
app.kubernetes.io/name: {{ include "k8sgpt.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
spec:
|
||||
{{- if .Values.deployment.securityContext }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.deployment.securityContext | nindent 8 }}
|
||||
{{ end -}}
|
||||
serviceAccountName: {{ template "k8sgpt.fullname" . }}
|
||||
containers:
|
||||
- name: k8sgpt-container
|
||||
|
||||
@@ -14,7 +14,10 @@ deployment:
|
||||
requests:
|
||||
cpu: "0.2"
|
||||
memory: "156Mi"
|
||||
|
||||
securityContext: {}
|
||||
# Set securityContext.runAsUser/runAsGroup if necessary. Values below were taken from https://github.com/k8sgpt-ai/k8sgpt/blob/main/container/Dockerfile
|
||||
# runAsUser: 65532
|
||||
# runAsGroup: 65532
|
||||
secret:
|
||||
secretKey: "" # base64 encoded OpenAI token
|
||||
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ var addCmd = &cobra.Command{
|
||||
_ = cmd.MarkFlagRequired("endpointname")
|
||||
_ = cmd.MarkFlagRequired("providerRegion")
|
||||
}
|
||||
if strings.ToLower(backend) == "amazonbedrock" {
|
||||
_ = cmd.MarkFlagRequired("providerRegion")
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
@@ -97,6 +100,10 @@ var addCmd = &cobra.Command{
|
||||
color.Red("Error: topP ranges from 0 to 1.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if topK < 1 || topK > 100 {
|
||||
color.Red("Error: topK ranges from 1 to 100.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if ai.NeedPassword(backend) && password == "" {
|
||||
fmt.Printf("Enter %s Key: ", backend)
|
||||
@@ -120,8 +127,11 @@ var addCmd = &cobra.Command{
|
||||
Temperature: temperature,
|
||||
ProviderRegion: providerRegion,
|
||||
ProviderId: providerId,
|
||||
CompartmentId: compartmentId,
|
||||
TopP: topP,
|
||||
TopK: topK,
|
||||
MaxTokens: maxTokens,
|
||||
OrganizationId: organizationId,
|
||||
}
|
||||
|
||||
if providerIndex == -1 {
|
||||
@@ -152,7 +162,9 @@ func init() {
|
||||
// add flag for endpointName
|
||||
addCmd.Flags().StringVarP(&endpointName, "endpointname", "n", "", "Endpoint Name, e.g. `endpoint-xxxxxxxxxxxx` (only for amazonbedrock, amazonsagemaker backends)")
|
||||
// add flag for topP
|
||||
addCmd.Flags().Float32VarP(&topP, "topp", "c", 0.5, "Probability Cutoff: Set a threshold (0.0-1.0) to limit word choices. Higher values add randomness, lower values increase predictability.")
|
||||
addCmd.Flags().Float32VarP(&topP, "topp", "", 0.5, "Probability Cutoff: Set a threshold (0.0-1.0) to limit word choices. Higher values add randomness, lower values increase predictability.")
|
||||
// add flag for topK
|
||||
addCmd.Flags().Int32VarP(&topK, "topk", "c", 50, "Sampling Cutoff: Set a threshold (1-100) to restrict the sampling process to the top K most probable words at each step. Higher values lead to greater variability, lower values increases predictability.")
|
||||
// max tokens
|
||||
addCmd.Flags().IntVarP(&maxTokens, "maxtokens", "l", 2048, "Specify a maximum output length. Adjust (1-...) to control text length. Higher values produce longer output, lower values limit length")
|
||||
// add flag for temperature
|
||||
@@ -163,4 +175,8 @@ func init() {
|
||||
addCmd.Flags().StringVarP(&providerRegion, "providerRegion", "r", "", "Provider Region name (only for amazonbedrock, googlevertexai backend)")
|
||||
//add flag for vertexAI Project ID
|
||||
addCmd.Flags().StringVarP(&providerId, "providerId", "i", "", "Provider specific ID for e.g. project (only for googlevertexai backend)")
|
||||
//add flag for OCI Compartment ID
|
||||
addCmd.Flags().StringVarP(&compartmentId, "compartmentId", "k", "", "Compartment ID for generative AI model (only for oci backend)")
|
||||
// add flag for openai organization
|
||||
addCmd.Flags().StringVarP(&organizationId, "organizationId", "o", "", "OpenAI or AzureOpenAI Organization ID (only for openai and azureopenai backend)")
|
||||
}
|
||||
|
||||
@@ -28,8 +28,11 @@ var (
|
||||
temperature float32
|
||||
providerRegion string
|
||||
providerId string
|
||||
compartmentId string
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
organizationId string
|
||||
)
|
||||
|
||||
var configAI ai.AIConfiguration
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
)
|
||||
|
||||
var details bool
|
||||
var userInput string
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
@@ -39,11 +38,6 @@ var listCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if details {
|
||||
fmt.Println("Show password ? (y/n)")
|
||||
fmt.Scan(&userInput)
|
||||
}
|
||||
|
||||
// Print the default if it is set
|
||||
fmt.Print(color.YellowString("Default: \n"))
|
||||
if configAI.DefaultProvider != "" {
|
||||
@@ -66,7 +60,7 @@ var listCmd = &cobra.Command{
|
||||
if details {
|
||||
for _, provider := range configAI.Providers {
|
||||
if provider.Name == aiBackend {
|
||||
printDetails(provider, userInput)
|
||||
printDetails(provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +85,7 @@ func init() {
|
||||
listCmd.Flags().BoolVar(&details, "details", false, "Print active provider configuration details")
|
||||
}
|
||||
|
||||
func printDetails(provider ai.AIProvider, userInput string) {
|
||||
func printDetails(provider ai.AIProvider) {
|
||||
if provider.Model != "" {
|
||||
fmt.Printf(" - Model: %s\n", provider.Model)
|
||||
}
|
||||
|
||||
@@ -26,13 +26,20 @@ var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update a backend provider",
|
||||
Long: "The command to update an AI backend provider",
|
||||
Args: cobra.ExactArgs(1),
|
||||
// Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
backend, _ := cmd.Flags().GetString("backend")
|
||||
if strings.ToLower(backend) == "azureopenai" {
|
||||
_ = cmd.MarkFlagRequired("engine")
|
||||
_ = cmd.MarkFlagRequired("baseurl")
|
||||
}
|
||||
organizationId, _ := cmd.Flags().GetString("organizationId")
|
||||
if strings.ToLower(backend) != "azureopenai" && strings.ToLower(backend) != "openai" {
|
||||
if organizationId != "" {
|
||||
color.Red("Error: organizationId must be empty for backends other than azureopenai or openai.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
@@ -43,50 +50,47 @@ var updateCmd = &cobra.Command{
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
inputBackends := strings.Split(args[0], ",")
|
||||
backend, _ := cmd.Flags().GetString("backend")
|
||||
|
||||
if len(inputBackends) == 0 {
|
||||
color.Red("Error: backend must be set.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if temperature > 1.0 || temperature < 0.0 {
|
||||
color.Red("Error: temperature ranges from 0 to 1.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for _, b := range inputBackends {
|
||||
foundBackend := false
|
||||
for i, provider := range configAI.Providers {
|
||||
if b == provider.Name {
|
||||
foundBackend = true
|
||||
if backend != "" {
|
||||
configAI.Providers[i].Name = backend
|
||||
color.Blue("Backend name updated successfully")
|
||||
}
|
||||
if model != "" {
|
||||
configAI.Providers[i].Model = model
|
||||
color.Blue("Model updated successfully")
|
||||
}
|
||||
if password != "" {
|
||||
configAI.Providers[i].Password = password
|
||||
color.Blue("Password updated successfully")
|
||||
}
|
||||
if baseURL != "" {
|
||||
configAI.Providers[i].BaseURL = baseURL
|
||||
color.Blue("Base URL updated successfully")
|
||||
}
|
||||
if engine != "" {
|
||||
configAI.Providers[i].Engine = engine
|
||||
}
|
||||
configAI.Providers[i].Temperature = temperature
|
||||
color.Green("%s updated in the AI backend provider list", b)
|
||||
foundBackend := false
|
||||
for i, provider := range configAI.Providers {
|
||||
if backend == provider.Name {
|
||||
foundBackend = true
|
||||
if backend != "" {
|
||||
configAI.Providers[i].Name = backend
|
||||
color.Blue("Backend name updated successfully")
|
||||
}
|
||||
if model != "" {
|
||||
configAI.Providers[i].Model = model
|
||||
color.Blue("Model updated successfully")
|
||||
}
|
||||
if password != "" {
|
||||
configAI.Providers[i].Password = password
|
||||
color.Blue("Password updated successfully")
|
||||
}
|
||||
if baseURL != "" {
|
||||
configAI.Providers[i].BaseURL = baseURL
|
||||
color.Blue("Base URL updated successfully")
|
||||
}
|
||||
if engine != "" {
|
||||
configAI.Providers[i].Engine = engine
|
||||
}
|
||||
if organizationId != "" {
|
||||
configAI.Providers[i].OrganizationId = organizationId
|
||||
color.Blue("Organization Id updated successfully")
|
||||
}
|
||||
configAI.Providers[i].Temperature = temperature
|
||||
color.Green("%s updated in the AI backend provider list", backend)
|
||||
}
|
||||
if !foundBackend {
|
||||
color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
if !foundBackend {
|
||||
color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", args[0])
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
viper.Set("ai", configAI)
|
||||
@@ -110,4 +114,6 @@ func init() {
|
||||
updateCmd.Flags().Float32VarP(&temperature, "temperature", "t", 0.7, "The sampling temperature, value ranges between 0 ( output be more deterministic) and 1 (more random)")
|
||||
// update flag for azure open ai engine/deployment name
|
||||
updateCmd.Flags().StringVarP(&engine, "engine", "e", "", "Update Azure AI deployment name")
|
||||
// update flag for organizationId
|
||||
updateCmd.Flags().StringVarP(&organizationId, "organizationId", "o", "", "Update OpenAI or Azure organization Id")
|
||||
}
|
||||
|
||||
11
cmd/cache/add.go
vendored
11
cmd/cache/add.go
vendored
@@ -30,6 +30,8 @@ var (
|
||||
storageAccount string
|
||||
containerName string
|
||||
projectId string
|
||||
endpoint string
|
||||
insecure bool
|
||||
)
|
||||
|
||||
// addCmd represents the add command
|
||||
@@ -48,7 +50,7 @@ var addCmd = &cobra.Command{
|
||||
}
|
||||
fmt.Println(color.YellowString("Adding remote based cache"))
|
||||
cacheType := args[0]
|
||||
remoteCache, err := cache.NewCacheProvider(cacheType, bucketname, region, storageAccount, containerName, projectId)
|
||||
remoteCache, err := cache.NewCacheProvider(cacheType, bucketName, region, endpoint, storageAccount, containerName, projectId, insecure)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
@@ -63,9 +65,10 @@ var addCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
CacheCmd.AddCommand(addCmd)
|
||||
addCmd.Flags().StringVarP(®ion, "region", "r", "", "The region to use for the AWS S3 or GCS cache")
|
||||
addCmd.Flags().StringVarP(&bucketname, "bucket", "b", "", "The name of the AWS S3 bucket to use for the cache")
|
||||
addCmd.MarkFlagsRequiredTogether("region", "bucket")
|
||||
addCmd.Flags().StringVarP(®ion, "region", "r", "us-east-1", "The region to use for the AWS S3 or GCS cache")
|
||||
addCmd.Flags().StringVarP(&endpoint, "endpoint", "e", "", "The S3 or minio endpoint")
|
||||
addCmd.Flags().BoolVarP(&insecure, "insecure", "i", false, "Skip TLS verification for S3/Minio custom endpoint")
|
||||
addCmd.Flags().StringVarP(&bucketName, "bucket", "b", "", "The name of the AWS S3 bucket to use for the cache")
|
||||
addCmd.Flags().StringVarP(&projectId, "projectid", "p", "", "The GCP project ID")
|
||||
addCmd.Flags().StringVarP(&storageAccount, "storageacc", "s", "", "The Azure storage account name of the container")
|
||||
addCmd.Flags().StringVarP(&containerName, "container", "c", "", "The Azure container name to use for the cache")
|
||||
|
||||
4
cmd/cache/cache.go
vendored
4
cmd/cache/cache.go
vendored
@@ -18,10 +18,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
bucketname string
|
||||
)
|
||||
|
||||
// cacheCmd represents the cache command
|
||||
var CacheCmd = &cobra.Command{
|
||||
Use: "cache",
|
||||
|
||||
@@ -15,6 +15,7 @@ package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
@@ -40,10 +41,9 @@ var listCmd = &cobra.Command{
|
||||
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
|
||||
fmt.Print(color.YellowString("Active: \n"))
|
||||
for _, filter := range activeFilters {
|
||||
|
||||
// if the filter is an integration, mark this differently
|
||||
// but if the integration is inactive, remove
|
||||
if util.SliceContainsString(integrationFilters, filter) {
|
||||
if slices.Contains(integrationFilters, filter) {
|
||||
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
|
||||
} else {
|
||||
// This strange bit of logic will loop through every integration via
|
||||
@@ -60,13 +60,12 @@ var listCmd = &cobra.Command{
|
||||
fmt.Print(color.YellowString("Unused: \n"))
|
||||
for _, filter := range inactiveFilters {
|
||||
// if the filter is an integration, mark this differently
|
||||
if util.SliceContainsString(integrationFilters, filter) {
|
||||
if slices.Contains(integrationFilters, filter) {
|
||||
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
|
||||
} else {
|
||||
fmt.Printf("> %s\n", color.RedString(filter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
|
||||
const (
|
||||
defaultTemperature float32 = 0.7
|
||||
defaultTopP float32 = 1.0
|
||||
defaultTopK int32 = 50
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -67,6 +69,38 @@ var ServeCmd = &cobra.Command{
|
||||
}
|
||||
return float32(temperature)
|
||||
}
|
||||
topP := func() float32 {
|
||||
env := os.Getenv("K8SGPT_TOP_P")
|
||||
if env == "" {
|
||||
return defaultTopP
|
||||
}
|
||||
topP, err := strconv.ParseFloat(env, 32)
|
||||
if err != nil {
|
||||
color.Red("Unable to convert topP value: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if topP > 1.0 || topP < 0.0 {
|
||||
color.Red("Error: topP ranges from 0 to 1.")
|
||||
os.Exit(1)
|
||||
}
|
||||
return float32(topP)
|
||||
}
|
||||
topK := func() int32 {
|
||||
env := os.Getenv("K8SGPT_TOP_K")
|
||||
if env == "" {
|
||||
return defaultTopK
|
||||
}
|
||||
topK, err := strconv.ParseFloat(env, 32)
|
||||
if err != nil {
|
||||
color.Red("Unable to convert topK value: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if topK < 10 || topK > 100 {
|
||||
color.Red("Error: topK ranges from 1 to 100.")
|
||||
os.Exit(1)
|
||||
}
|
||||
return int32(topK)
|
||||
}
|
||||
// Check for env injection
|
||||
backend = os.Getenv("K8SGPT_BACKEND")
|
||||
password := os.Getenv("K8SGPT_PASSWORD")
|
||||
@@ -79,13 +113,15 @@ var ServeCmd = &cobra.Command{
|
||||
envIsSet := backend != "" || password != "" || model != ""
|
||||
if envIsSet {
|
||||
aiProvider = &ai.AIProvider{
|
||||
Name: backend,
|
||||
Password: password,
|
||||
Model: model,
|
||||
BaseURL: baseURL,
|
||||
Engine: engine,
|
||||
Name: backend,
|
||||
Password: password,
|
||||
Model: model,
|
||||
BaseURL: baseURL,
|
||||
Engine: engine,
|
||||
ProxyEndpoint: proxyEndpoint,
|
||||
Temperature: temperature(),
|
||||
Temperature: temperature(),
|
||||
TopP: topP(),
|
||||
TopK: topK(),
|
||||
}
|
||||
|
||||
configAI.Providers = append(configAI.Providers, *aiProvider)
|
||||
|
||||
224
go.mod
224
go.mod
@@ -1,46 +1,54 @@
|
||||
module github.com/k8sgpt-ai/k8sgpt
|
||||
|
||||
go 1.21
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.22.4
|
||||
|
||||
require (
|
||||
github.com/aquasecurity/trivy-operator v0.17.1
|
||||
github.com/fatih/color v1.16.0
|
||||
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.5
|
||||
github.com/sashabaranov/go-openai v1.20.2
|
||||
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/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/term v0.17.0
|
||||
helm.sh/helm/v3 v3.13.3
|
||||
k8s.io/api v0.28.4
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/client-go v0.28.4
|
||||
k8s.io/kubectl v0.28.4 // indirect
|
||||
golang.org/x/term v0.21.0
|
||||
helm.sh/helm/v3 v3.15.2
|
||||
k8s.io/api v0.30.2
|
||||
k8s.io/apimachinery v0.30.2
|
||||
k8s.io/client-go v0.30.2
|
||||
k8s.io/kubectl v0.30.2 // indirect
|
||||
|
||||
)
|
||||
|
||||
require github.com/adrg/xdg v0.4.0
|
||||
|
||||
require (
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc-ecosystem/gateway/v2 v2.19.1-20240213144542-6e830f3fdf19.1
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/grpc/go v1.3.0-20240213144542-6e830f3fdf19.2
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.32.0-20240213144542-6e830f3fdf19.1
|
||||
cloud.google.com/go/storage v1.38.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
|
||||
github.com/aws/aws-sdk-go v1.50.34
|
||||
github.com/cohere-ai/cohere-go v0.2.0
|
||||
github.com/google/generative-ai-go v0.8.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
|
||||
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.3.0-20240406062209-1cc152efbf5c.3
|
||||
buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go v1.34.2-20240717144446-c4efcc29ff16.2
|
||||
cloud.google.com/go/storage v1.40.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.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/prometheus/prometheus v0.49.1
|
||||
github.com/oracle/oci-go-sdk/v65 v65.65.1
|
||||
github.com/prometheus/prometheus v0.53.1
|
||||
github.com/pterm/pterm v0.12.79
|
||||
google.golang.org/api v0.167.0
|
||||
google.golang.org/api v0.183.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
sigs.k8s.io/controller-runtime v0.16.3
|
||||
sigs.k8s.io/controller-runtime v0.18.4
|
||||
sigs.k8s.io/gateway-api v1.0.0
|
||||
)
|
||||
|
||||
@@ -48,63 +56,70 @@ 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.0 // indirect
|
||||
cloud.google.com/go/ai v0.3.0 // indirect
|
||||
cloud.google.com/go/aiplatform v1.59.0 // indirect
|
||||
cloud.google.com/go/compute v1.24.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.6 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.5 // indirect
|
||||
cloud.google.com/go/vertexai v0.7.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
cloud.google.com/go v0.114.0 // indirect
|
||||
cloud.google.com/go/ai v0.3.5-0.20240409161017-ce55ad694f21 // indirect
|
||||
cloud.google.com/go/aiplatform v1.67.0 // indirect
|
||||
cloud.google.com/go/auth v0.5.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.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
|
||||
github.com/Microsoft/hcsshim v0.12.4 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect
|
||||
github.com/cohere-ai/tokenizer v1.1.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect
|
||||
github.com/gorilla/websocket v1.5.2 // 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/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
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.23.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
gopkg.in/evanphx/json-patch.v5 v5.7.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
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-20240528184218-531527333157 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // 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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
@@ -118,52 +133,51 @@ require (
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20231020043206-3770774790ce // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd v1.7.11 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/cli v26.1.4+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/docker v27.0.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-errors/errors v1.5.1 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.7.0
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-containerregistry v0.16.1 // indirect
|
||||
github.com/google/go-containerregistry v0.17.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
@@ -184,24 +198,23 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.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
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.54.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rubenv/sql-migrate v1.5.2 // indirect
|
||||
github.com/rubenv/sql-migrate v1.6.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spdx/tools-golang v0.5.3 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
@@ -212,40 +225,39 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.opentelemetry.io/otel v1.23.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20231016134836-22325403fcb3 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.starlark.net v0.0.0-20240520160348-046347dcd104 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/oauth2 v0.17.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
|
||||
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/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/appengine v1.6.8 // indirect
|
||||
google.golang.org/grpc v1.62.0
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
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
|
||||
k8s.io/apiextensions-apiserver v0.28.4
|
||||
k8s.io/apiserver v0.28.4 // indirect
|
||||
k8s.io/cli-runtime v0.28.4 // indirect
|
||||
k8s.io/component-base v0.28.4 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
|
||||
k8s.io/utils v0.0.0-20240102154912-e7106e64919e
|
||||
oras.land/oras-go v1.2.4 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.30.2
|
||||
k8s.io/apiserver v0.30.2 // indirect
|
||||
k8s.io/cli-runtime v0.30.2 // indirect
|
||||
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-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.15.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.15.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.17.2 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
|
||||
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
|
||||
replace oras.land/oras-go => oras.land/oras-go v1.2.4
|
||||
//replace oras.land/oras-go => oras.land/oras-go v1.2.4
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@@ -76,6 +77,9 @@ func GetModelOrDefault(model string) string {
|
||||
// GetModelOrDefault check config region
|
||||
func GetRegionOrDefault(region string) string {
|
||||
|
||||
if os.Getenv("AWS_DEFAULT_REGION") != "" {
|
||||
region = os.Getenv("AWS_DEFAULT_REGION")
|
||||
}
|
||||
// Check if the provided model is in the list
|
||||
for _, m := range BEDROCKER_SUPPORTED_REGION {
|
||||
if m == region {
|
||||
|
||||
@@ -33,6 +33,7 @@ type SageMakerAIClient struct {
|
||||
temperature float32
|
||||
endpoint string
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
@@ -56,6 +57,7 @@ type Message struct {
|
||||
type Parameters struct {
|
||||
MaxNewTokens int `json:"max_new_tokens"`
|
||||
TopP float64 `json:"top_p"`
|
||||
TopK float64 `json:"top_k"`
|
||||
Temperature float64 `json:"temperature"`
|
||||
}
|
||||
|
||||
@@ -74,6 +76,7 @@ func (c *SageMakerAIClient) Configure(config IAIConfig) error {
|
||||
c.temperature = config.GetTemperature()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -90,6 +93,7 @@ func (c *SageMakerAIClient) GetCompletion(_ context.Context, prompt string) (str
|
||||
Parameters: Parameters{
|
||||
MaxNewTokens: int(c.maxTokens),
|
||||
TopP: float64(c.topP),
|
||||
TopK: float64(c.topK),
|
||||
Temperature: float64(c.temperature),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ const azureAIClientName = "azureopenai"
|
||||
type AzureAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *openai.Client
|
||||
model string
|
||||
temperature float32
|
||||
client *openai.Client
|
||||
model string
|
||||
temperature float32
|
||||
organizationId string
|
||||
}
|
||||
|
||||
func (c *AzureAIClient) Configure(config IAIConfig) error {
|
||||
@@ -25,6 +26,7 @@ func (c *AzureAIClient) Configure(config IAIConfig) error {
|
||||
engine := config.GetEngine()
|
||||
proxyEndpoint := config.GetProxyEndpoint()
|
||||
defaultConfig := openai.DefaultAzureConfig(token, baseURL)
|
||||
orgId := config.GetOrganizationId()
|
||||
|
||||
defaultConfig.AzureModelMapperFunc = func(model string) string {
|
||||
// If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function
|
||||
@@ -34,7 +36,7 @@ func (c *AzureAIClient) Configure(config IAIConfig) error {
|
||||
return azureModelMapping[model]
|
||||
|
||||
}
|
||||
|
||||
|
||||
if proxyEndpoint != "" {
|
||||
proxyUrl, err := url.Parse(proxyEndpoint)
|
||||
if err != nil {
|
||||
@@ -48,6 +50,10 @@ func (c *AzureAIClient) Configure(config IAIConfig) error {
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
if orgId != "" {
|
||||
defaultConfig.OrgID = orgId
|
||||
}
|
||||
|
||||
client := openai.NewClientWithConfig(defaultConfig)
|
||||
if client == nil {
|
||||
return errors.New("error creating Azure OpenAI client")
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/cohere-ai/cohere-go"
|
||||
api "github.com/cohere-ai/cohere-go/v2"
|
||||
cohere "github.com/cohere-ai/cohere-go/v2/client"
|
||||
"github.com/cohere-ai/cohere-go/v2/option"
|
||||
)
|
||||
|
||||
const cohereAIClientName = "cohere"
|
||||
@@ -28,45 +30,49 @@ type CohereClient struct {
|
||||
client *cohere.Client
|
||||
model string
|
||||
temperature float32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
func (c *CohereClient) Configure(config IAIConfig) error {
|
||||
token := config.GetPassword()
|
||||
|
||||
client, err := cohere.CreateClient(token)
|
||||
if err != nil {
|
||||
return err
|
||||
opts := []option.RequestOption{
|
||||
cohere.WithToken(token),
|
||||
}
|
||||
|
||||
baseURL := config.GetBaseURL()
|
||||
if baseURL != "" {
|
||||
client.BaseURL = baseURL
|
||||
opts = append(opts, cohere.WithBaseURL(baseURL))
|
||||
}
|
||||
|
||||
client := cohere.NewClient(opts...)
|
||||
if client == nil {
|
||||
return errors.New("error creating Cohere client")
|
||||
}
|
||||
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CohereClient) GetCompletion(_ context.Context, prompt string) (string, error) {
|
||||
func (c *CohereClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
resp, err := c.client.Generate(cohere.GenerateOptions{
|
||||
Model: c.model,
|
||||
Prompt: prompt,
|
||||
MaxTokens: cohere.Uint(2048),
|
||||
Temperature: cohere.Float64(float64(c.temperature)),
|
||||
K: cohere.Int(0),
|
||||
StopSequences: []string{},
|
||||
ReturnLikelihoods: "NONE",
|
||||
response, err := c.client.Chat(ctx, &api.ChatRequest{
|
||||
Message: prompt,
|
||||
Model: &c.model,
|
||||
K: api.Int(0),
|
||||
Preamble: api.String(""),
|
||||
Temperature: api.Float64(float64(c.temperature)),
|
||||
RawPrompting: api.Bool(false),
|
||||
MaxTokens: api.Int(c.maxTokens),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Generations[0].Text, nil
|
||||
return response.Text, nil
|
||||
}
|
||||
|
||||
func (c *CohereClient) GetName() string {
|
||||
|
||||
@@ -31,6 +31,7 @@ type GoogleGenAIClient struct {
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
@@ -53,6 +54,7 @@ func (c *GoogleGenAIClient) Configure(config IAIConfig) error {
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
return nil
|
||||
}
|
||||
@@ -62,6 +64,7 @@ func (c *GoogleGenAIClient) GetCompletion(ctx context.Context, prompt string) (s
|
||||
model := c.client.GenerativeModel(c.model)
|
||||
model.SetTemperature(c.temperature)
|
||||
model.SetTopP(c.topP)
|
||||
model.SetTopK(c.topK)
|
||||
model.SetMaxOutputTokens(int32(c.maxTokens))
|
||||
|
||||
// Google AI SDK is capable of different inputs than just text, for now set explicit text prompt type.
|
||||
|
||||
@@ -30,6 +30,7 @@ type GoogleVertexAIClient struct {
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
@@ -111,6 +112,7 @@ func (g *GoogleVertexAIClient) Configure(config IAIConfig) error {
|
||||
g.model = GetVertexAIModelOrDefault(config.GetModel())
|
||||
g.temperature = config.GetTemperature()
|
||||
g.topP = config.GetTopP()
|
||||
g.topK = config.GetTopK()
|
||||
g.maxTokens = config.GetMaxTokens()
|
||||
|
||||
return nil
|
||||
@@ -121,6 +123,7 @@ func (g *GoogleVertexAIClient) GetCompletion(ctx context.Context, prompt string)
|
||||
model := g.client.GenerativeModel(g.model)
|
||||
model.SetTemperature(g.temperature)
|
||||
model.SetTopP(g.topP)
|
||||
model.SetTopK(float32(g.topK))
|
||||
model.SetMaxOutputTokens(int32(g.maxTokens))
|
||||
|
||||
// Google AI SDK is capable of different inputs than just text, for now set explicit text prompt type.
|
||||
|
||||
@@ -2,6 +2,7 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hupe1980/go-huggingface"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
@@ -14,6 +15,7 @@ type HuggingfaceClient struct {
|
||||
client *huggingface.InferenceClient
|
||||
model string
|
||||
topP float32
|
||||
topK int32
|
||||
temperature float32
|
||||
maxTokens int
|
||||
}
|
||||
@@ -26,6 +28,7 @@ func (c *HuggingfaceClient) Configure(config IAIConfig) error {
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
c.temperature = config.GetTemperature()
|
||||
if config.GetMaxTokens() > 500 {
|
||||
c.maxTokens = 500
|
||||
@@ -43,6 +46,7 @@ func (c *HuggingfaceClient) GetCompletion(ctx context.Context, prompt string) (s
|
||||
Model: c.model,
|
||||
Parameters: huggingface.ConversationalParameters{
|
||||
TopP: ptr.To[float64](float64(c.topP)),
|
||||
TopK: ptr.To[int](int(c.topK)),
|
||||
Temperature: ptr.To[float64](float64(c.temperature)),
|
||||
MaxLength: &c.maxTokens,
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +23,7 @@ var (
|
||||
&OpenAIClient{},
|
||||
&AzureAIClient{},
|
||||
&LocalAIClient{},
|
||||
&OllamaClient{},
|
||||
&NoOpAIClient{},
|
||||
&CohereClient{},
|
||||
&AmazonBedRockClient{},
|
||||
@@ -29,10 +31,13 @@ var (
|
||||
&GoogleGenAIClient{},
|
||||
&HuggingfaceClient{},
|
||||
&GoogleVertexAIClient{},
|
||||
&OCIGenAIClient{},
|
||||
&WatsonxAIClient{},
|
||||
}
|
||||
Backends = []string{
|
||||
openAIClientName,
|
||||
localAIClientName,
|
||||
ollamaClientName,
|
||||
azureAIClientName,
|
||||
cohereAIClientName,
|
||||
amazonbedrockAIClientName,
|
||||
@@ -41,6 +46,8 @@ var (
|
||||
noopAIClientName,
|
||||
huggingfaceAIClientName,
|
||||
googleVertexAIClientName,
|
||||
ociClientName,
|
||||
watsonxAIClientName,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -72,8 +79,12 @@ type IAIConfig interface {
|
||||
GetTemperature() float32
|
||||
GetProviderRegion() string
|
||||
GetTopP() float32
|
||||
GetTopK() int32
|
||||
GetMaxTokens() int
|
||||
GetProviderId() string
|
||||
GetCompartmentId() string
|
||||
GetOrganizationId() string
|
||||
GetCustomHeaders() []http.Header
|
||||
}
|
||||
|
||||
func NewClient(provider string) IAI {
|
||||
@@ -92,19 +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"`
|
||||
TopP float32 `mapstructure:"topp" yaml:"topp,omitempty"`
|
||||
MaxTokens int `mapstructure:"maxtokens" yaml:"maxtokens,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 {
|
||||
@@ -123,6 +138,10 @@ func (p *AIProvider) GetTopP() float32 {
|
||||
return p.TopP
|
||||
}
|
||||
|
||||
func (p *AIProvider) GetTopK() int32 {
|
||||
return p.TopK
|
||||
}
|
||||
|
||||
func (p *AIProvider) GetMaxTokens() int {
|
||||
return p.MaxTokens
|
||||
}
|
||||
@@ -150,7 +169,19 @@ func (p *AIProvider) GetProviderId() string {
|
||||
return p.ProviderId
|
||||
}
|
||||
|
||||
var passwordlessProviders = []string{"localai", "amazonsagemaker", "amazonbedrock", "googlevertexai"}
|
||||
func (p *AIProvider) GetCompartmentId() string {
|
||||
return p.CompartmentId
|
||||
}
|
||||
|
||||
func (p *AIProvider) GetOrganizationId() string {
|
||||
return p.OrganizationId
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
97
pkg/ai/ocigenai.go
Normal file
97
pkg/ai/ocigenai.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2024 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"
|
||||
"github.com/oracle/oci-go-sdk/v65/common"
|
||||
"github.com/oracle/oci-go-sdk/v65/generativeaiinference"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ociClientName = "oci"
|
||||
|
||||
type OCIGenAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *generativeaiinference.GenerativeAiInferenceClient
|
||||
model string
|
||||
compartmentId string
|
||||
temperature float32
|
||||
topP float32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) GetName() string {
|
||||
return ociClientName
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) Configure(config IAIConfig) error {
|
||||
config.GetEndpointName()
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
c.compartmentId = config.GetCompartmentId()
|
||||
provider := common.DefaultConfigProvider()
|
||||
client, err := generativeaiinference.NewGenerativeAiInferenceClientWithConfigurationProvider(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.client = &client
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
generateTextRequest := c.newGenerateTextRequest(prompt)
|
||||
generateTextResponse, err := c.client.GenerateText(ctx, generateTextRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractGeneratedText(generateTextResponse.InferenceResponse)
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) newGenerateTextRequest(prompt string) generativeaiinference.GenerateTextRequest {
|
||||
temperatureF64 := float64(c.temperature)
|
||||
topPF64 := float64(c.topP)
|
||||
return generativeaiinference.GenerateTextRequest{
|
||||
GenerateTextDetails: generativeaiinference.GenerateTextDetails{
|
||||
CompartmentId: &c.compartmentId,
|
||||
ServingMode: generativeaiinference.OnDemandServingMode{
|
||||
ModelId: &c.model,
|
||||
},
|
||||
InferenceRequest: generativeaiinference.CohereLlmInferenceRequest{
|
||||
Prompt: &prompt,
|
||||
MaxTokens: &c.maxTokens,
|
||||
Temperature: &temperatureF64,
|
||||
TopP: &topPF64,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func extractGeneratedText(llmInferenceResponse generativeaiinference.LlmInferenceResponse) (string, error) {
|
||||
response, ok := llmInferenceResponse.(generativeaiinference.CohereLlmInferenceResponse)
|
||||
if !ok {
|
||||
return "", errors.New("failed to extract generated text from backed response")
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
for _, text := range response.GeneratedTexts {
|
||||
if text.Text != nil {
|
||||
sb.WriteString(*text.Text)
|
||||
}
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -27,9 +27,11 @@ const openAIClientName = "openai"
|
||||
type OpenAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *openai.Client
|
||||
model string
|
||||
temperature float32
|
||||
client *openai.Client
|
||||
model string
|
||||
temperature float32
|
||||
topP float32
|
||||
organizationId string
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -37,12 +39,12 @@ const (
|
||||
maxToken = 2048
|
||||
presencePenalty = 0.0
|
||||
frequencyPenalty = 0.0
|
||||
topP = 1.0
|
||||
)
|
||||
|
||||
func (c *OpenAIClient) Configure(config IAIConfig) error {
|
||||
token := config.GetPassword()
|
||||
defaultConfig := openai.DefaultConfig(token)
|
||||
orgId := config.GetOrganizationId()
|
||||
proxyEndpoint := config.GetProxyEndpoint()
|
||||
|
||||
baseURL := config.GetBaseURL()
|
||||
@@ -50,20 +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")
|
||||
@@ -71,6 +80,7 @@ func (c *OpenAIClient) Configure(config IAIConfig) error {
|
||||
c.client = client
|
||||
c.model = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -88,7 +98,7 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
|
||||
MaxTokens: maxToken,
|
||||
PresencePenalty: presencePenalty,
|
||||
FrequencyPenalty: frequencyPenalty,
|
||||
TopP: topP,
|
||||
TopP: c.topP,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -99,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,
|
||||
}
|
||||
|
||||
84
pkg/ai/watsonxai.go
Normal file
84
pkg/ai/watsonxai.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func (c *WatsonxAIClient) Configure(config IAIConfig) error {
|
||||
if(config.GetModel() == "") {
|
||||
c.model = config.GetModel()
|
||||
} else {
|
||||
c.model = modelMetallama
|
||||
}
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
c.maxNewTokens = config.GetMaxTokens()
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -170,6 +176,8 @@ func (a *Analysis) RunCustomAnalysis() {
|
||||
|
||||
result, err := canClient.Run()
|
||||
if err != nil {
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("[%s] %s", cAnalyzer.Name, err))
|
||||
} else {
|
||||
a.Results = append(a.Results, result)
|
||||
}
|
||||
}
|
||||
@@ -195,6 +203,7 @@ func (a *Analysis) RunAnalysis() {
|
||||
Client: a.Client,
|
||||
Context: a.Context,
|
||||
Namespace: a.Namespace,
|
||||
LabelSelector: a.LabelSelector,
|
||||
AIClient: a.AIClient,
|
||||
OpenapiSchema: openapiSchema,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/cache"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
@@ -26,8 +31,6 @@ import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// sub-function
|
||||
@@ -85,6 +88,7 @@ func analysis_RunAnalysisFilterTester(t *testing.T, filterFlag string) []common.
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
WithDoc: true,
|
||||
}
|
||||
if len(filterFlag) > 0 {
|
||||
// `--filter` is explicitly given
|
||||
@@ -133,6 +137,10 @@ func TestAnalysis_RunAnalysisActiveFilter(t *testing.T) {
|
||||
viper.SetDefault("active_filters", []string{"Ingress", "Service", "Pod"})
|
||||
results = analysis_RunAnalysisFilterTester(t, "")
|
||||
assert.Equal(t, len(results), 3)
|
||||
|
||||
// Invalid filter
|
||||
results = analysis_RunAnalysisFilterTester(t, "invalid")
|
||||
assert.Equal(t, len(results), 0)
|
||||
}
|
||||
|
||||
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
|
||||
@@ -279,3 +287,120 @@ func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
|
||||
|
||||
require.Equal(t, got, expected)
|
||||
}
|
||||
|
||||
func TestNewAnalysis(t *testing.T) {
|
||||
disabledCache := cache.New("disabled-cache")
|
||||
disabledCache.DisableCache()
|
||||
aiClient := &ai.NoOpAIClient{}
|
||||
results := []common.Result{
|
||||
{
|
||||
Kind: "VulnerabilityReport",
|
||||
Error: []common.Failure{
|
||||
{
|
||||
Text: "This is a custom failure",
|
||||
KubernetesDoc: "test-kubernetes-doc",
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Masked: "masked-error",
|
||||
Unmasked: "unmasked-error",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a Analysis
|
||||
output string
|
||||
anonymize bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "Empty results",
|
||||
a: Analysis{},
|
||||
},
|
||||
{
|
||||
name: "cache disabled",
|
||||
a: Analysis{
|
||||
AIClient: aiClient,
|
||||
Cache: disabledCache,
|
||||
Results: results,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "output and anonymize both set",
|
||||
a: Analysis{
|
||||
AIClient: aiClient,
|
||||
Cache: cache.New("test-cache"),
|
||||
Results: results,
|
||||
},
|
||||
output: "test-output",
|
||||
anonymize: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.a.GetAIResults(tt.output, tt.anonymize)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAIResultForSanitizedFailures(t *testing.T) {
|
||||
enabledCache := cache.New("enabled-cache")
|
||||
disabledCache := cache.New("disabled-cache")
|
||||
disabledCache.DisableCache()
|
||||
aiClient := &ai.NoOpAIClient{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a Analysis
|
||||
texts []string
|
||||
promptTmpl string
|
||||
expectedOutput string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "Cache enabled",
|
||||
a: Analysis{
|
||||
AIClient: aiClient,
|
||||
Cache: enabledCache,
|
||||
},
|
||||
texts: []string{"some-data"},
|
||||
expectedOutput: "I am a noop response to the prompt %!(EXTRA string=, string=some-data)",
|
||||
},
|
||||
{
|
||||
name: "cache disabled",
|
||||
a: Analysis{
|
||||
AIClient: aiClient,
|
||||
Cache: disabledCache,
|
||||
Language: "English",
|
||||
},
|
||||
texts: []string{"test input"},
|
||||
promptTmpl: "Response in %s: %s",
|
||||
expectedOutput: "I am a noop response to the prompt Response in English: test input",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := tt.a.getAIResultForSanitizedFailures(tt.texts, tt.promptTmpl)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedOutput, output)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
require.Empty(t, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +78,10 @@ func (a *Analysis) textOutput() ([]byte, error) {
|
||||
return []byte(output.String()), nil
|
||||
}
|
||||
for n, result := range a.Results {
|
||||
output.WriteString(fmt.Sprintf("%s %s(%s)\n", color.CyanString("%d", n),
|
||||
color.YellowString(result.Name), color.CyanString(result.ParentObject)))
|
||||
output.WriteString(fmt.Sprintf("%s: %s %s(%s)\n", color.CyanString("%d", n),
|
||||
color.HiYellowString(result.Kind),
|
||||
color.YellowString(result.Name),
|
||||
color.CyanString(result.ParentObject)))
|
||||
for _, err := range result.Error {
|
||||
output.WriteString(fmt.Sprintf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text)))
|
||||
if err.KubernetesDoc != "" {
|
||||
|
||||
64
pkg/analysis/output_test.go
Normal file
64
pkg/analysis/output_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2024 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 analysis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrintOutput(t *testing.T) {
|
||||
require.NotEmpty(t, getOutputFormats())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a *Analysis
|
||||
format string
|
||||
expectedOutput string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "json format",
|
||||
a: &Analysis{},
|
||||
format: "json",
|
||||
expectedOutput: "{\n \"provider\": \"\",\n \"errors\": null,\n \"status\": \"OK\",\n \"problems\": 0,\n \"results\": null\n}",
|
||||
},
|
||||
{
|
||||
name: "text format",
|
||||
a: &Analysis{},
|
||||
format: "text",
|
||||
expectedOutput: "AI Provider: AI not used; --explain not set\n\nNo problems detected\n",
|
||||
},
|
||||
{
|
||||
name: "unsupported format",
|
||||
a: &Analysis{},
|
||||
format: "unsupported",
|
||||
expectedErr: "unsupported output format: unsupported. Available format",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
output, err := tt.a.PrintOutput(tt.format)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(output), tt.expectedOutput)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
require.Nil(t, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -123,15 +123,15 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, cronJob.Name, cronJob.Namespace).Set(float64(len(failures)))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
currentAnalysis := common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
for key, value := range preAnalysis {
|
||||
currentAnalysis := common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
|
||||
@@ -15,219 +15,186 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestCronJobSuccess(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*/1 * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
func TestCronJobAnalyzer(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: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
}
|
||||
|
||||
func TestCronJobBroken(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
|
||||
assert.Equal(t, analysisResults[0].Kind, "CronJob")
|
||||
}
|
||||
|
||||
func TestCronJobBrokenMultipleNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ1",
|
||||
// This CronJob won't be list because of namespace filtering.
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ2",
|
||||
Namespace: "default",
|
||||
},
|
||||
// A suspended CronJob will contribute to failures.
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Suspend: suspend,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ3",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Valid schedule
|
||||
Schedule: "*/1 * * * *",
|
||||
|
||||
// Negative starting deadline
|
||||
StartingDeadlineSeconds: invalidStartingDeadline,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ4",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Invalid schedule
|
||||
Schedule: "*** * * * *",
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ5",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Valid schedule
|
||||
Schedule: "*/1 * * * *",
|
||||
|
||||
// Positive starting deadline shouldn't be any problem.
|
||||
StartingDeadlineSeconds: validStartingDeadline,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
// This cronjob shouldn't contribute to any failures.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "successful-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*/1 * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "other-namespace",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
cjAnalyzer := CronJobAnalyzer{}
|
||||
results, err := cjAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
expectations := []string{
|
||||
"default/CJ2",
|
||||
"default/CJ3",
|
||||
"default/CJ4",
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
|
||||
assert.Equal(t, analysisResults[0].Kind, "CronJob")
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, result := range results {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
|
||||
|
||||
// get the list of events
|
||||
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
|
||||
metav1.ListOptions{
|
||||
FieldSelector: "involvedObject.name=" + name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// find most recent event
|
||||
var latestEvent *v1.Event
|
||||
for _, event := range events.Items {
|
||||
if latestEvent == nil {
|
||||
// this is required, as a pointer to a loop variable would always yield the latest value in the range
|
||||
e := event
|
||||
latestEvent = &e
|
||||
}
|
||||
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
|
||||
// this is required, as a pointer to a loop variable would always yield the latest value in the range
|
||||
e := event
|
||||
latestEvent = &e
|
||||
}
|
||||
}
|
||||
return latestEvent, nil
|
||||
}
|
||||
@@ -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().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -140,8 +140,10 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -531,3 +531,37 @@ func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "hpa",
|
||||
},
|
||||
},
|
||||
},
|
||||
&autoscalingv1.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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -163,8 +163,10 @@ func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.Ingress.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,146 +15,226 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestIngressAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
validIgClassName := new(string)
|
||||
*validIgClassName = "valid-ingress-class"
|
||||
|
||||
var igRule networkingv1.IngressRule
|
||||
|
||||
httpRule := networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service exists.
|
||||
Name: "Service1",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
{
|
||||
Path: "/test1",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service is in the test namespace
|
||||
// Hence, it won't be discovered.
|
||||
Name: "Service2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/test2",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service doesn't exist.
|
||||
Name: "Service3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
igRule.IngressRuleValue.HTTP = &httpRule
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
Client: fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
// Doesn't specify an ingress class.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress2",
|
||||
Namespace: "default",
|
||||
// Specify an invalid ingress class name using annotations.
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "invalid-class",
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
// Namespace filtering.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress3",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&networkingv1.IngressClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: *validIgClassName,
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress4",
|
||||
Namespace: "default",
|
||||
// Specify valid ingress class name using annotations.
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": *validIgClassName,
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Namespace filtering.
|
||||
Name: "Service2",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Secret1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Secret2",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress5",
|
||||
Namespace: "default",
|
||||
},
|
||||
|
||||
func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
)
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
// Specify valid ingress class name in spec.
|
||||
Spec: networkingv1.IngressSpec{
|
||||
IngressClassName: validIgClassName,
|
||||
Rules: []networkingv1.IngressRule{
|
||||
igRule,
|
||||
},
|
||||
TLS: []networkingv1.IngressTLS{
|
||||
{
|
||||
// This won't contribute to any failures.
|
||||
SecretName: "Secret1",
|
||||
},
|
||||
{
|
||||
// This secret won't be discovered because of namespace filtering.
|
||||
SecretName: "Secret2",
|
||||
},
|
||||
{
|
||||
// This secret doesn't exist.
|
||||
SecretName: "Secret3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
igAnalyzer := IngressAnalyzer{}
|
||||
results, err := igAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/Ingress1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/Ingress2",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/Ingress5",
|
||||
failuresCount: 4,
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 2)
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
func TestIngressAnalyzerLabelSelectorFiltering(t *testing.T) {
|
||||
validIgClassName := new(string)
|
||||
*validIgClassName = "valid-ingress-class"
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
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",
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=ingress",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
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)
|
||||
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err.Text, "does not specify an Ingress class") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Error("expected error 'does not specify an Ingress class' not found in analysis results")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "other-namespace",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
rawlogs := string(podLogs)
|
||||
if errorPattern.MatchString(strings.ToLower(rawlogs)) {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: printErrorLines(pod.Name, pod.Namespace, rawlogs, errorPattern),
|
||||
Text: printErrorLines(rawlogs, errorPattern),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: pod.Name,
|
||||
@@ -96,14 +96,16 @@ func (LogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
func printErrorLines(podName, namespace, logs string, errorPattern *regexp.Regexp) string {
|
||||
func printErrorLines(logs string, errorPattern *regexp.Regexp) string {
|
||||
// Split the logs into lines
|
||||
logLines := strings.Split(logs, "\n")
|
||||
|
||||
|
||||
173
pkg/analyzer/log_test.go
Normal file
173
pkg/analyzer/log_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestLogAnalyzer(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{
|
||||
"Name": "Pod1",
|
||||
"Namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "test-container1",
|
||||
},
|
||||
{
|
||||
Name: "test-container2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod2",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"Name": "Pod1",
|
||||
"Namespace": "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod3",
|
||||
Namespace: "test-namespace",
|
||||
Labels: map[string]string{
|
||||
"Name": "Pod1",
|
||||
"Namespace": "test-namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod4",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"Name": "Pod4",
|
||||
"Namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "test-container3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
logAnalyzer := LogAnalyzer{}
|
||||
results, err := logAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
expectations := []string{"default/Pod1/test-container1", "default/Pod1/test-container2", "default/Pod4/test-container3"}
|
||||
|
||||
for i, expectation := range expectations {
|
||||
require.Equal(t, expectation, results[i].Name)
|
||||
|
||||
for _, failure := range results[i].Error {
|
||||
require.Equal(t, "fake logs", failure.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -151,8 +151,10 @@ func (MutatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, erro
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.MutatingWebhook.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.MutatingWebhook.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -136,6 +136,19 @@ func TestNetpolWithPod(t *testing.T) {
|
||||
|
||||
func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "policy-without-podselector-match-labels",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PodSelector: metav1.LabelSelector{
|
||||
// len(MatchLabels) == 0 should trigger a failure.
|
||||
// Allowing traffic to all pods.
|
||||
MatchLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
@@ -203,7 +216,50 @@ func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(results), 1)
|
||||
assert.Equal(t, len(results), 2)
|
||||
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)
|
||||
@@ -74,8 +77,10 @@ func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,110 +15,203 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestNodeAnalyzerNodeReady(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "KubeletReady",
|
||||
Message: "kubelet is posting ready status",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
func TestNodeAnalyzer(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
}
|
||||
|
||||
func TestNodeAnalyzerNodeDiskPressure(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "KubeletHasDiskPressure",
|
||||
Message: "kubelet has disk pressure",
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Node{
|
||||
// A node without Status Conditions shouldn't contribute to failures.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node1",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Node{
|
||||
// Nodes are not filtered using namespace.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
// Won't contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
// Will contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
// Will contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
// Non-false statuses for the default cases contribute to failures.
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
// A cloud provider may set their own condition and/or a new status
|
||||
// might be introduced. In such cases a failure is assumed and
|
||||
// the code shouldn't break, although it might be a false positive.
|
||||
{
|
||||
Type: "UnknownNodeConditionType",
|
||||
Status: "CompletelyUnknown",
|
||||
},
|
||||
// These won't contribute to failures.
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Node{
|
||||
// A node without any failures shouldn't be present in the results.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node3",
|
||||
Namespace: "test",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
// Won't contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
nAnalyzer := NodeAnalyzer{}
|
||||
results, err := nAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "Node2",
|
||||
failuresCount: 11,
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
// A cloud provider may set their own condition and/or a new status might be introduced
|
||||
// In such cases a failure is assumed and the code shouldn't break, although it might be a false positive
|
||||
func TestNodeAnalyzerNodeUnknownType(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: "UnknownNodeConditionType",
|
||||
Status: "CompletelyUnknown",
|
||||
Reason: "KubeletHasTheUnknown",
|
||||
Message: "kubelet has the unknown",
|
||||
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",
|
||||
}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
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
|
||||
}
|
||||
@@ -99,8 +99,10 @@ func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -112,11 +112,97 @@ func TestPodDisruptionBudgetAnalyzer(t *testing.T) {
|
||||
pdbAnalyzer := PdbAnalyzer{}
|
||||
results, err := pdbAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, result := range results {
|
||||
require.Equal(t, "test/PDB3", result.Name)
|
||||
for _, failure := range result.Error {
|
||||
require.Contains(t, failure.Text, "expected pdb pod label")
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -33,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
|
||||
}
|
||||
@@ -41,12 +44,12 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
for _, pod := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check for pending pods
|
||||
if pod.Status.Phase == "Pending" {
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, containerStatus := range pod.Status.Conditions {
|
||||
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
|
||||
if containerStatus.Type == v1.PodScheduled && containerStatus.Reason == "Unschedulable" {
|
||||
if containerStatus.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: containerStatus.Message,
|
||||
@@ -57,60 +60,12 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check through container status to check for crashes or unready
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
// Check for errors in the init containers.
|
||||
failures = append(failures, analyzeContainerStatusFailures(a, pod.Status.InitContainerStatuses, pod.Name, pod.Namespace, string(pod.Status.Phase))...)
|
||||
|
||||
if containerStatus.State.Waiting != nil {
|
||||
// Check for errors in containers.
|
||||
failures = append(failures, analyzeContainerStatusFailures(a, pod.Status.ContainerStatuses, pod.Name, pod.Namespace, string(pod.Status.Phase))...)
|
||||
|
||||
if isErrorReason(containerStatus.State.Waiting.Reason) && containerStatus.State.Waiting.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: containerStatus.State.Waiting.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
|
||||
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if isEvtErrorReason(evt.Reason) && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled
|
||||
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("the last termination reason is %s container=%s pod=%s", containerStatus.LastTerminationState.Terminated.Reason, containerStatus.Name, pod.Name),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// when pod is Running but its ReadinessProbe fails
|
||||
if !containerStatus.Ready && pod.Status.Phase == "Running" {
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "Unhealthy" && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
|
||||
Pod: pod,
|
||||
@@ -127,14 +82,68 @@ func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
|
||||
func analyzeContainerStatusFailures(a common.Analyzer, statuses []v1.ContainerStatus, name string, namespace string, statusPhase string) []common.Failure {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check through container status to check for crashes or unready
|
||||
for _, containerStatus := range statuses {
|
||||
if containerStatus.State.Waiting != nil {
|
||||
if containerStatus.State.Waiting.Reason == "ContainerCreating" && statusPhase == "Pending" {
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
// parse the event log and append details
|
||||
evt, err := util.FetchLatestEvent(a.Context, a.Client, namespace, name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if isEvtErrorReason(evt.Reason) && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
} else if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" && containerStatus.LastTerminationState.Terminated != nil {
|
||||
// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("the last termination reason is %s container=%s pod=%s", containerStatus.LastTerminationState.Terminated.Reason, containerStatus.Name, name),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
} else if isErrorReason(containerStatus.State.Waiting.Reason) && containerStatus.State.Waiting.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: containerStatus.State.Waiting.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// when pod is Running but its ReadinessProbe fails
|
||||
if !containerStatus.Ready && statusPhase == "Running" {
|
||||
// parse the event log and append details
|
||||
evt, err := util.FetchLatestEvent(a.Context, a.Client, namespace, name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "Unhealthy" && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failures
|
||||
}
|
||||
|
||||
func isErrorReason(reason string) bool {
|
||||
failureReasons := []string{
|
||||
"CrashLoopBackOff", "ImagePullBackOff", "CreateContainerConfigError", "PreCreateHookError", "CreateContainerError",
|
||||
|
||||
@@ -15,144 +15,357 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestPodAnalyzer(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
|
||||
},
|
||||
tests := []struct {
|
||||
name string
|
||||
config common.Analyzer
|
||||
expectations []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "Pending pods, namespace filtering and readiness probe failure",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
// This condition will contribute to failures.
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
|
||||
},
|
||||
{
|
||||
// This condition won't contribute to failures.
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unexpected failure",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
// This pod won't be selected because of namespace filtering.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod2",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod3",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
// When pod is Running but its ReadinessProbe fails
|
||||
Phase: v1.PodRunning,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Ready: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event1",
|
||||
Namespace: "default",
|
||||
},
|
||||
InvolvedObject: v1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "Pod3",
|
||||
Namespace: "default",
|
||||
},
|
||||
Reason: "Unhealthy",
|
||||
Message: "readiness probe failed: the detail reason here ...",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeWarning,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example2",
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "example2",
|
||||
Ready: false,
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/Pod1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.ContainersReady,
|
||||
Reason: "ContainersNotReady",
|
||||
Message: "containers with unready status: [example2]",
|
||||
},
|
||||
{
|
||||
name: "default/Pod3",
|
||||
failuresCount: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
// simulate event: 30s Warning Unhealthy pod/my-nginx-7fb4dbcf47-4ch4w Readiness probe failed: bash: xxxx: command not found
|
||||
&v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
{
|
||||
name: "readiness probe failure without any event",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
// When pod is Running but its ReadinessProbe fails
|
||||
// It won't contribute to any failures because
|
||||
// there's no event present.
|
||||
Phase: v1.PodRunning,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Ready: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
InvolvedObject: v1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "example2",
|
||||
Namespace: "default",
|
||||
UID: "differentUid",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Reason: "Unhealthy",
|
||||
Message: "readiness probe failed: the detail reason here ...",
|
||||
Source: v1.EventSource{Component: "eventTest"},
|
||||
Count: 1,
|
||||
Type: v1.EventTypeWarning,
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
podAnalyzer := PodAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := podAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 2)
|
||||
}
|
||||
|
||||
func TestPodAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
{
|
||||
name: "Init container status state waiting",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
InitContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Ready: true,
|
||||
State: v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{
|
||||
StartedAt: metav1.Now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
Reason: "ContainerCreating",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event1",
|
||||
Namespace: "default",
|
||||
},
|
||||
InvolvedObject: v1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Reason: "FailedCreatePodSandBox",
|
||||
Message: "failed to create the pod sandbox ...",
|
||||
Type: v1.EventTypeWarning,
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/Pod1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "other-namespace",
|
||||
Annotations: map[string]string{},
|
||||
{
|
||||
name: "Container status state waiting but no event reported",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
Reason: "ContainerCreating",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Container status state waiting",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "Container1",
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
Reason: "ContainerCreating",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Container2",
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// This represents container that is in CrashLoopBackOff state due to conditions such as OOMKilled
|
||||
Reason: "CrashLoopBackOff",
|
||||
},
|
||||
},
|
||||
LastTerminationState: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
Reason: "test reason",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Container3",
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// This won't contribute to failures.
|
||||
Reason: "RandomReason",
|
||||
Message: "This container won't be present in the failures",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Container4",
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// Valid error reason.
|
||||
Reason: "PreStartHookError",
|
||||
Message: "Container4 encountered PreStartHookError",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Container5",
|
||||
Ready: false,
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
// Valid error reason.
|
||||
Reason: "CrashLoopBackOff",
|
||||
Message: "Container4 encountered CrashLoopBackOff",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event1",
|
||||
Namespace: "default",
|
||||
},
|
||||
InvolvedObject: v1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
Name: "Pod1",
|
||||
Namespace: "default",
|
||||
},
|
||||
// This reason won't contribute to failures.
|
||||
Reason: "RandomEvent",
|
||||
Type: v1.EventTypeWarning,
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/Pod1",
|
||||
failuresCount: 3,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
podAnalyzer := PodAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := podAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results, err := podAnalyzer.Analyze(tt.config)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tt.expectations == nil {
|
||||
require.Equal(t, 0, len(results))
|
||||
} else {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
require.Equal(t, len(tt.expectations), len(results))
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, tt.expectations[i].name, result.Name)
|
||||
require.Equal(t, tt.expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
if pvc.Status.Phase == appsv1.ClaimPending {
|
||||
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)
|
||||
evt, err := util.FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
@@ -74,8 +74,10 @@ func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
@@ -38,13 +39,15 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&appsv1.Event{
|
||||
// This is the latest 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 provisioning failed",
|
||||
Message: "PVC Event1 provisioning failed",
|
||||
},
|
||||
&appsv1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -54,10 +57,16 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
&appsv1.Event{
|
||||
// This is the latest event.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event3",
|
||||
Namespace: "default",
|
||||
},
|
||||
LastTimestamp: metav1.Time{
|
||||
Time: time.Date(2024, 4, 15, 10, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Reason: "ProvisioningFailed",
|
||||
Message: "PVC Event3 provisioning failed",
|
||||
},
|
||||
&appsv1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -214,11 +223,58 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
|
||||
for i, expectation := range tt.expectations {
|
||||
require.Equal(t, expectation, results[i].Name)
|
||||
for _, failure := range results[i].Error {
|
||||
require.Equal(t, "PVC provisioning failed", failure.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -72,8 +72,10 @@ func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
|
||||
@@ -124,30 +124,78 @@ func TestReplicaSetAnalyzer(t *testing.T) {
|
||||
})
|
||||
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresText []string
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/ReplicaSet1",
|
||||
failuresText: []string{
|
||||
"failed to create test replica set 1",
|
||||
},
|
||||
name: "default/ReplicaSet1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/ReplicaSet4",
|
||||
failuresText: []string{
|
||||
"failed to create test replica set 4 condition 1",
|
||||
"failed to create test replica set 4 condition 3",
|
||||
},
|
||||
name: "default/ReplicaSet4",
|
||||
failuresCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, expectation := range expectations {
|
||||
require.Equal(t, expectation.name, results[i].Name)
|
||||
for j, failure := range results[i].Error {
|
||||
require.Equal(t, expectation.failuresText[j], failure.Text)
|
||||
}
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
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
|
||||
}
|
||||
@@ -128,8 +128,10 @@ func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
|
||||
@@ -145,21 +145,16 @@ func TestServiceAnalyzer(t *testing.T) {
|
||||
})
|
||||
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresText []string
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "test/Endpoint1",
|
||||
failuresText: []string{
|
||||
"Service has not ready endpoints, pods",
|
||||
},
|
||||
name: "test/Endpoint1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "test/Service1",
|
||||
failuresText: []string{
|
||||
"Service has no endpoints, expected label",
|
||||
"Service has no endpoints, expected label",
|
||||
},
|
||||
name: "test/Service1",
|
||||
failuresCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -167,8 +162,109 @@ func TestServiceAnalyzer(t *testing.T) {
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
for j, failure := range result.Error {
|
||||
require.Contains(t, failure.Text, expectations[i].failuresText[j])
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -109,8 +109,10 @@ func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
"ReadWriteOnce",
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Resources: corev1.VolumeResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
@@ -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
|
||||
}
|
||||
@@ -149,8 +149,10 @@ func (ValidatingWebhookAnalyzer) Analyze(a common.Analyzer) ([]common.Result, er
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.ValidatingWebhook.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
parent, found := util.GetParent(a.Client, value.ValidatingWebhook.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
4
pkg/cache/cache.go
vendored
4
pkg/cache/cache.go
vendored
@@ -47,7 +47,7 @@ func ParseCacheConfiguration() (CacheProvider, error) {
|
||||
return cacheInfo, nil
|
||||
}
|
||||
|
||||
func NewCacheProvider(cacheType, bucketname, region, storageAccount, containerName, projectId string) (CacheProvider, error) {
|
||||
func NewCacheProvider(cacheType, bucketname, region, endpoint, storageAccount, containerName, projectId string, insecure bool) (CacheProvider, error) {
|
||||
cProvider := CacheProvider{}
|
||||
|
||||
switch {
|
||||
@@ -61,6 +61,8 @@ func NewCacheProvider(cacheType, bucketname, region, storageAccount, containerNa
|
||||
case cacheType == "s3":
|
||||
cProvider.S3.BucketName = bucketname
|
||||
cProvider.S3.Region = region
|
||||
cProvider.S3.Endpoint = endpoint
|
||||
cProvider.S3.InsecureSkipVerify = insecure
|
||||
default:
|
||||
return CacheProvider{}, status.Error(codes.Internal, fmt.Sprintf("%s is not a valid option", cacheType))
|
||||
}
|
||||
|
||||
20
pkg/cache/s3_based.go
vendored
20
pkg/cache/s3_based.go
vendored
@@ -2,7 +2,9 @@ package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@@ -17,17 +19,16 @@ type S3Cache struct {
|
||||
}
|
||||
|
||||
type S3CacheConfiguration struct {
|
||||
Region string `mapstructure:"region" yaml:"region,omitempty"`
|
||||
BucketName string `mapstructure:"bucketname" yaml:"bucketname,omitempty"`
|
||||
Region string `mapstructure:"region" yaml:"region,omitempty"`
|
||||
BucketName string `mapstructure:"bucketname" yaml:"bucketname,omitempty"`
|
||||
Endpoint string `mapstructure:"endpoint" yaml:"endpoint,omitempty"`
|
||||
InsecureSkipVerify bool `mapstructure:"insecure" yaml:"insecure,omitempty"`
|
||||
}
|
||||
|
||||
func (s *S3Cache) Configure(cacheInfo CacheProvider) error {
|
||||
if cacheInfo.S3.BucketName == "" {
|
||||
log.Fatal("Bucket name not configured")
|
||||
}
|
||||
if cacheInfo.S3.Region == "" {
|
||||
log.Fatal("Region not configured")
|
||||
}
|
||||
s.bucketName = cacheInfo.S3.BucketName
|
||||
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
@@ -36,6 +37,15 @@ func (s *S3Cache) Configure(cacheInfo CacheProvider) error {
|
||||
Region: aws.String(cacheInfo.S3.Region),
|
||||
},
|
||||
}))
|
||||
if cacheInfo.S3.Endpoint != "" {
|
||||
sess.Config.Endpoint = &cacheInfo.S3.Endpoint
|
||||
sess.Config.S3ForcePathStyle = aws.Bool(true)
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: cacheInfo.S3.InsecureSkipVerify},
|
||||
}
|
||||
customClient := &http.Client{Transport: transport}
|
||||
sess.Config.HTTPClient = customClient
|
||||
}
|
||||
|
||||
s3Client := s3.New(sess)
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
openapi_v2 "github.com/google/gnostic/openapiv2"
|
||||
"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"
|
||||
@@ -37,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
|
||||
@@ -62,8 +65,11 @@ type PreAnalysis struct {
|
||||
Gateway gtwapi.Gateway
|
||||
HTTPRoute gtwapi.HTTPRoute
|
||||
// Integrations
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
TrivyConfigAuditReport trivy.ConfigAuditReport
|
||||
ScaledObject keda.ScaledObject
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
TrivyConfigAuditReport trivy.ConfigAuditReport
|
||||
KyvernoPolicyReport kyverno.PolicyReport
|
||||
KyvernoClusterPolicyReport kyverno.ClusterPolicyReport
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
)
|
||||
|
||||
type AWS struct {
|
||||
@@ -23,16 +24,28 @@ func (a *AWS) UnDeploy(namespace string) error {
|
||||
}
|
||||
|
||||
func (a *AWS) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
|
||||
// Check for AWS credentials in the environment
|
||||
// https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
|
||||
if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" {
|
||||
panic("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set in the environment")
|
||||
// Retrieve AWS credentials from the environment
|
||||
accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
|
||||
secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
||||
awsProfile := os.Getenv("AWS_PROFILE")
|
||||
|
||||
var sess *session.Session
|
||||
if accessKeyID != "" && secretAccessKey != "" {
|
||||
// Use access keys if both are provided
|
||||
sess = session.Must(session.NewSessionWithOptions(session.Options{
|
||||
Config: aws.Config{},
|
||||
}))
|
||||
} else {
|
||||
// Use AWS profile, default to "default" if not set
|
||||
if awsProfile == "" {
|
||||
awsProfile = "default"
|
||||
}
|
||||
sess = session.Must(session.NewSessionWithOptions(session.Options{
|
||||
Profile: awsProfile,
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
}
|
||||
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: aws.Config{},
|
||||
}))
|
||||
a.sess = sess
|
||||
(*mergedMap)["EKS"] = &EKSAnalyzer{
|
||||
session: a.sess,
|
||||
|
||||
@@ -16,9 +16,12 @@ package integration
|
||||
import (
|
||||
"errors"
|
||||
"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"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/prometheus"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
@@ -49,6 +52,8 @@ var integrations = map[string]IIntegration{
|
||||
"trivy": trivy.NewTrivy(),
|
||||
"prometheus": prometheus.NewPrometheus(),
|
||||
"aws": aws.NewAWS(),
|
||||
"keda": keda.NewKeda(),
|
||||
"kyverno": kyverno.NewKyverno(),
|
||||
}
|
||||
|
||||
func NewIntegration() *Integration {
|
||||
@@ -89,7 +94,7 @@ func (*Integration) Activate(name string, namespace string, activeFilters []stri
|
||||
|
||||
if !skipInstall {
|
||||
if err := integrations[name].Deploy(namespace); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to deploy %s integration: %w", name, err)
|
||||
}
|
||||
}
|
||||
mergedFilters := activeFilters
|
||||
@@ -124,7 +129,7 @@ func (*Integration) Deactivate(name string, namespace string) error {
|
||||
}
|
||||
|
||||
if err := integrations[name].UnDeploy(namespace); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to undeploy %s integration: %w", name, err)
|
||||
}
|
||||
|
||||
viper.Set("active_filters", activeFilters)
|
||||
|
||||
142
pkg/integration/integration_test.go
Normal file
142
pkg/integration/integration_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2024 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 integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalyzerByIntegration(t *testing.T) {
|
||||
integration := NewIntegration()
|
||||
_, err := integration.Get("invalid-name")
|
||||
require.ErrorContains(t, err, "integration not found")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedName string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "random",
|
||||
expectedErr: "analyzerbyintegration: no matches found",
|
||||
},
|
||||
{
|
||||
name: "PrometheusConfigValidate",
|
||||
expectedName: "prometheus",
|
||||
},
|
||||
{
|
||||
name: "PrometheusConfigRelabelReport",
|
||||
expectedName: "prometheus",
|
||||
},
|
||||
{
|
||||
name: "VulnerabilityReport",
|
||||
expectedName: "trivy",
|
||||
},
|
||||
{
|
||||
name: "ConfigAuditReport",
|
||||
expectedName: "trivy",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
name, err := integration.AnalyzerByIntegration(tt.name)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedName, name)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
require.Empty(t, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivate(t *testing.T) {
|
||||
integration := NewIntegration()
|
||||
err := integration.Activate("prometheus", "", []string{}, true)
|
||||
require.ErrorContains(t, err, "error writing config file:")
|
||||
|
||||
err = integration.Deactivate("prometheus", "")
|
||||
require.ErrorContains(t, err, "error writing config file:")
|
||||
|
||||
configFileName := "config.json"
|
||||
_, err = os.CreateTemp("", configFileName)
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(configFileName)
|
||||
|
||||
// Set the configuration file in viper
|
||||
viper.SetConfigType("json")
|
||||
viper.SetConfigFile(configFileName)
|
||||
|
||||
inteNotFoundErr := "integration not found"
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
activeFilters []string
|
||||
skipInstall bool
|
||||
expectedIsActivate bool
|
||||
expectedActivationErr string
|
||||
expectedIsActivateError string
|
||||
expectedDeactivationErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid integration",
|
||||
expectedActivationErr: inteNotFoundErr,
|
||||
expectedIsActivateError: inteNotFoundErr,
|
||||
expectedDeactivationErr: inteNotFoundErr,
|
||||
},
|
||||
{
|
||||
name: "prometheus",
|
||||
skipInstall: true,
|
||||
expectedIsActivate: true,
|
||||
},
|
||||
{
|
||||
name: "trivy",
|
||||
skipInstall: false,
|
||||
expectedActivationErr: "failed to deploy trivy integration:",
|
||||
expectedDeactivationErr: "failed to undeploy trivy integration:",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := integration.Activate(tt.name, tt.namespace, tt.activeFilters, tt.skipInstall)
|
||||
if tt.expectedActivationErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedActivationErr)
|
||||
}
|
||||
|
||||
ok, err := integration.IsActivate(tt.name)
|
||||
if tt.expectedIsActivateError == "" {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expectedIsActivate, ok)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedIsActivateError)
|
||||
}
|
||||
|
||||
err = integration.Deactivate(tt.name, tt.namespace)
|
||||
if tt.expectedDeactivationErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedDeactivationErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
229
pkg/integration/keda/keda.go
Normal file
229
pkg/integration/keda/keda.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package keda
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/typed/keda/v1alpha1"
|
||||
helmclient "github.com/mittwald/go-helm-client"
|
||||
"github.com/spf13/viper"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
var (
|
||||
Repo = getEnv("KEDA_REPO", "https://kedacore.github.io/charts")
|
||||
Version = getEnv("KEDA_VERSION", "2.11.2")
|
||||
ChartName = getEnv("KEDA_CHART_NAME", "keda")
|
||||
RepoShortName = getEnv("KEDA_REPO_SHORT_NAME", "keda")
|
||||
ReleaseName = getEnv("KEDA_RELEASE_NAME", "keda-k8sgpt")
|
||||
)
|
||||
|
||||
type Keda struct {
|
||||
helm helmclient.Client
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func NewKeda() *Keda {
|
||||
helmClient, err := helmclient.New(&helmclient.Options{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &Keda{
|
||||
helm: helmClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keda) Deploy(namespace string) error {
|
||||
// Add the repository
|
||||
chartRepo := repo.Entry{
|
||||
Name: RepoShortName,
|
||||
URL: Repo,
|
||||
}
|
||||
// Add a chart-repository to the client.
|
||||
if err := k.helm.AddOrUpdateChartRepo(chartRepo); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
chartSpec := helmclient.ChartSpec{
|
||||
ReleaseName: ReleaseName,
|
||||
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
|
||||
Namespace: namespace,
|
||||
|
||||
//TODO: All of this should be configurable
|
||||
UpgradeCRDs: true,
|
||||
Wait: false,
|
||||
Timeout: 300,
|
||||
CreateNamespace: true,
|
||||
}
|
||||
|
||||
// Install a chart release.
|
||||
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
|
||||
if _, err := k.helm.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keda) UnDeploy(namespace string) error {
|
||||
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)
|
||||
}
|
||||
|
||||
kedaNamespace, _ := k.GetNamespace()
|
||||
color.Blue(fmt.Sprintf("Keda namespace: %s\n", kedaNamespace))
|
||||
|
||||
kClient, _ := v1alpha1.NewForConfig(client.Config)
|
||||
|
||||
scaledObjectList, _ := kClient.ScaledObjects("").List(context.Background(), v1.ListOptions{})
|
||||
scaledJobList, _ := kClient.ScaledJobs("").List(context.Background(), v1.ListOptions{})
|
||||
triggerAuthenticationList, _ := kClient.TriggerAuthentications("").List(context.Background(), v1.ListOptions{})
|
||||
clusterTriggerAuthenticationsList, _ := kClient.ClusterTriggerAuthentications().List(context.Background(), v1.ListOptions{})
|
||||
|
||||
// Before uninstalling the Helm chart, we need to delete Keda resources
|
||||
for _, scaledObject := range scaledObjectList.Items {
|
||||
err := kClient.ScaledObjects(scaledObject.Namespace).Delete(context.Background(), scaledObject.Name, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting scaledObject %s: %v\n", scaledObject.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Deleted scaledObject %s in namespace %s\n", scaledObject.Name, scaledObject.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, scaledJob := range scaledJobList.Items {
|
||||
err := kClient.ScaledJobs(scaledJob.Namespace).Delete(context.Background(), scaledJob.Name, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting scaledJob %s: %v\n", scaledJob.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Deleted scaledJob %s in namespace %s\n", scaledJob.Name, scaledJob.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, triggerAuthentication := range triggerAuthenticationList.Items {
|
||||
err := kClient.TriggerAuthentications(triggerAuthentication.Namespace).Delete(context.Background(), triggerAuthentication.Name, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting triggerAuthentication %s: %v\n", triggerAuthentication.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Deleted triggerAuthentication %s in namespace %s\n", triggerAuthentication.Name, triggerAuthentication.Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, clusterTriggerAuthentication := range clusterTriggerAuthenticationsList.Items {
|
||||
err := kClient.ClusterTriggerAuthentications().Delete(context.Background(), clusterTriggerAuthentication.Name, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
fmt.Printf("Error deleting clusterTriggerAuthentication %s: %v\n", clusterTriggerAuthentication.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Deleted clusterTriggerAuthentication %s\n", clusterTriggerAuthentication.Name)
|
||||
}
|
||||
}
|
||||
|
||||
chartSpec := helmclient.ChartSpec{
|
||||
ReleaseName: ReleaseName,
|
||||
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
|
||||
Namespace: namespace,
|
||||
UpgradeCRDs: true,
|
||||
Wait: false,
|
||||
Timeout: 300,
|
||||
}
|
||||
// Uninstall the chart release.
|
||||
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
|
||||
if err := k.helm.UninstallRelease(&chartSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Keda) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
|
||||
(*mergedMap)["ScaledObject"] = &ScaledObjectAnalyzer{}
|
||||
}
|
||||
|
||||
func (k *Keda) GetAnalyzerName() []string {
|
||||
return []string{
|
||||
"ScaledObject",
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keda) GetNamespace() (string, error) {
|
||||
releases, err := k.helm.ListDeployedReleases()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, rel := range releases {
|
||||
if rel.Name == ReleaseName {
|
||||
return rel.Namespace, nil
|
||||
}
|
||||
}
|
||||
return "", status.Error(codes.NotFound, "keda release not found")
|
||||
}
|
||||
|
||||
func (k *Keda) OwnsAnalyzer(analyzer string) bool {
|
||||
for _, a := range k.GetAnalyzerName() {
|
||||
if analyzer == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Keda) 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 *Keda) isDeployed() bool {
|
||||
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 == "keda.sh" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (k *Keda) IsActivate() bool {
|
||||
return k.isFilterActive() && k.isDeployed()
|
||||
}
|
||||
193
pkg/integration/keda/scaledobject_analyzer.go
Normal file
193
pkg/integration/keda/scaledobject_analyzer.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package keda
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
kedaSchema "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
|
||||
"github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/typed/keda/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ScaledObjectAnalyzer struct{}
|
||||
|
||||
func (s *ScaledObjectAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
kClient, _ := v1alpha1.NewForConfig(a.Client.GetConfig())
|
||||
kind := "ScaledObject"
|
||||
|
||||
apiDoc := kubernetes.K8sApiReference{
|
||||
Kind: kind,
|
||||
ApiVersion: kedaSchema.GroupVersion,
|
||||
OpenapiSchema: a.OpenapiSchema,
|
||||
}
|
||||
|
||||
list, err := kClient.ScaledObjects(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, so := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
scaleTargetRef := so.Spec.ScaleTargetRef
|
||||
if scaleTargetRef.Kind == "" {
|
||||
scaleTargetRef.Kind = "Deployment"
|
||||
}
|
||||
|
||||
var podInfo PodInfo
|
||||
|
||||
switch scaleTargetRef.Kind {
|
||||
case "Deployment":
|
||||
deployment, err := a.Client.GetClient().AppsV1().Deployments(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
podInfo = DeploymentInfo{deployment}
|
||||
}
|
||||
case "ReplicationController":
|
||||
rc, err := a.Client.GetClient().CoreV1().ReplicationControllers(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
podInfo = ReplicationControllerInfo{rc}
|
||||
}
|
||||
case "ReplicaSet":
|
||||
rs, err := a.Client.GetClient().AppsV1().ReplicaSets(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
podInfo = ReplicaSetInfo{rs}
|
||||
}
|
||||
case "StatefulSet":
|
||||
ss, err := a.Client.GetClient().AppsV1().StatefulSets(so.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
podInfo = StatefulSetInfo{ss}
|
||||
}
|
||||
default:
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("ScaledObject uses %s as ScaleTargetRef which is not an option.", scaleTargetRef.Kind),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
|
||||
if podInfo == nil {
|
||||
doc := apiDoc.GetApiDocV2("spec.scaleTargetRef")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("ScaledObject uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: scaleTargetRef.Name,
|
||||
Masked: util.MaskString(scaleTargetRef.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
containers := len(podInfo.GetPodSpec().Containers)
|
||||
for _, container := range podInfo.GetPodSpec().Containers {
|
||||
for _, trigger := range so.Spec.Triggers {
|
||||
if trigger.Type == "cpu" || trigger.Type == "memory" {
|
||||
if container.Resources.Requests == nil || container.Resources.Limits == nil {
|
||||
containers--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if containers <= 0 {
|
||||
doc := apiDoc.GetApiDocV2("spec.scaleTargetRef.kind")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s %s/%s does not have resource configured.", scaleTargetRef.Kind, so.Namespace, scaleTargetRef.Name),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: scaleTargetRef.Name,
|
||||
Masked: util.MaskString(scaleTargetRef.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
evt, err := util.FetchLatestEvent(a.Context, a.Client, so.Namespace, so.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if evt.Type != "Normal" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: scaleTargetRef.Name,
|
||||
Masked: util.MaskString(scaleTargetRef.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", so.Namespace, so.Name)] = common.PreAnalysis{
|
||||
ScaledObject: so,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.ScaledObject.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
|
||||
type PodInfo interface {
|
||||
GetPodSpec() corev1.PodSpec
|
||||
}
|
||||
|
||||
type DeploymentInfo struct {
|
||||
*appsv1.Deployment
|
||||
}
|
||||
|
||||
func (d DeploymentInfo) GetPodSpec() corev1.PodSpec {
|
||||
return d.Spec.Template.Spec
|
||||
}
|
||||
|
||||
// define a structure for ReplicationController
|
||||
type ReplicationControllerInfo struct {
|
||||
*corev1.ReplicationController
|
||||
}
|
||||
|
||||
func (rc ReplicationControllerInfo) GetPodSpec() corev1.PodSpec {
|
||||
return rc.Spec.Template.Spec
|
||||
}
|
||||
|
||||
// define a structure for ReplicaSet
|
||||
type ReplicaSetInfo struct {
|
||||
*appsv1.ReplicaSet
|
||||
}
|
||||
|
||||
func (rs ReplicaSetInfo) GetPodSpec() corev1.PodSpec {
|
||||
return rs.Spec.Template.Spec
|
||||
}
|
||||
|
||||
// define a structure for StatefulSet
|
||||
type StatefulSetInfo struct {
|
||||
*appsv1.StatefulSet
|
||||
}
|
||||
|
||||
// implement PodInfo for StatefulSetInfo
|
||||
func (ss StatefulSetInfo) GetPodSpec() corev1.PodSpec {
|
||||
return ss.Spec.Template.Spec
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (p *Prometheus) Deploy(namespace string) error {
|
||||
// manage Prometheus on the behalf of users.
|
||||
podConfigs, err := findPrometheusPodConfigs(ctx, client.GetClient(), namespace)
|
||||
if err != nil {
|
||||
color.Red("Error discovering Prometheus worklads: %v", err)
|
||||
color.Red("Error discovering Prometheus workloads: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(podConfigs) == 0 {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func TestSliceContainsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeContext string
|
||||
kubeConfig string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "empty config and empty context",
|
||||
kubeContext: "",
|
||||
kubeConfig: "",
|
||||
expectedErr: "invalid configuration: no configuration has been provided, try setting KUBERNETES_MASTER environment variable",
|
||||
},
|
||||
{
|
||||
name: "non empty config and empty context",
|
||||
kubeContext: "",
|
||||
kubeConfig: "kube-config",
|
||||
expectedErr: "stat kube-config: no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "empty config and non empty context",
|
||||
kubeContext: "some-context",
|
||||
kubeConfig: "",
|
||||
expectedErr: "context \"some-context\" does not exist",
|
||||
},
|
||||
{
|
||||
name: "non empty config and non empty context",
|
||||
kubeContext: "minikube",
|
||||
kubeConfig: "./testdata/kubeconfig",
|
||||
expectedErr: "Get \"https://192.168.49.2:8443/version\": dial tcp 192.168.49.2:8443",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client, err := NewClient(tt.kubeContext, tt.kubeConfig)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
require.Nil(t, client)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesClient(t *testing.T) {
|
||||
client := Client{
|
||||
Config: &rest.Config{
|
||||
Host: "host",
|
||||
},
|
||||
}
|
||||
|
||||
require.NotEmpty(t, client.GetConfig())
|
||||
require.Nil(t, client.GetClient())
|
||||
require.Nil(t, client.GetCtrlClient())
|
||||
}
|
||||
16
pkg/kubernetes/testdata/kubeconfig
vendored
16
pkg/kubernetes/testdata/kubeconfig
vendored
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://192.168.49.2:8443
|
||||
name: minikube
|
||||
contexts:
|
||||
- context:
|
||||
cluster: minikube
|
||||
namespace: default
|
||||
user: minikube
|
||||
name: minikube
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
@@ -25,11 +25,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 {
|
||||
|
||||
@@ -9,6 +9,16 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
notUsedBucket = ""
|
||||
notUsedRegion = ""
|
||||
notUsedEndpoint = ""
|
||||
notUsedStorageAcc = ""
|
||||
notUsedContainerName = ""
|
||||
notUsedProjectId = ""
|
||||
notUsedInsecure = false
|
||||
)
|
||||
|
||||
func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (*schemav1.AddConfigResponse, error,
|
||||
) {
|
||||
|
||||
@@ -23,11 +33,11 @@ func (h *handler) AddConfig(ctx context.Context, i *schemav1.AddConfigRequest) (
|
||||
|
||||
switch i.Cache.GetCacheType().(type) {
|
||||
case *schemav1.Cache_AzureCache:
|
||||
remoteCache, err = cache.NewCacheProvider("azure", "", "", i.Cache.GetAzureCache().StorageAccount, i.Cache.GetAzureCache().ContainerName, "")
|
||||
remoteCache, err = cache.NewCacheProvider("azure", notUsedBucket, notUsedRegion, notUsedEndpoint, i.Cache.GetAzureCache().StorageAccount, i.Cache.GetAzureCache().ContainerName, notUsedProjectId, notUsedInsecure)
|
||||
case *schemav1.Cache_S3Cache:
|
||||
remoteCache, err = cache.NewCacheProvider("s3", i.Cache.GetS3Cache().BucketName, i.Cache.GetS3Cache().Region, "", "", "")
|
||||
remoteCache, err = cache.NewCacheProvider("s3", i.Cache.GetS3Cache().BucketName, i.Cache.GetS3Cache().Region, i.Cache.GetS3Cache().Endpoint, notUsedStorageAcc, notUsedContainerName, notUsedProjectId, i.Cache.GetS3Cache().Insecure)
|
||||
case *schemav1.Cache_GcsCache:
|
||||
remoteCache, err = cache.NewCacheProvider("gcs", i.Cache.GetGcsCache().BucketName, i.Cache.GetGcsCache().Region, "", "", i.Cache.GetGcsCache().GetProjectId())
|
||||
remoteCache, err = cache.NewCacheProvider("gcs", i.Cache.GetGcsCache().BucketName, i.Cache.GetGcsCache().Region, notUsedEndpoint, notUsedStorageAcc, notUsedContainerName, i.Cache.GetGcsCache().GetProjectId(), notUsedInsecure)
|
||||
default:
|
||||
return resp, status.Error(codes.InvalidArgument, "Invalid cache configuration")
|
||||
}
|
||||
|
||||
108
pkg/util/util.go
108
pkg/util/util.go
@@ -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"
|
||||
@@ -33,15 +36,6 @@ import (
|
||||
|
||||
var anonymizePattern = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;':\",./<>?")
|
||||
|
||||
func SliceContainsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) {
|
||||
if meta.OwnerReferences != nil {
|
||||
for _, owner := range meta.OwnerReferences {
|
||||
@@ -54,7 +48,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if rs.OwnerReferences != nil {
|
||||
return GetParent(client, rs.ObjectMeta)
|
||||
}
|
||||
return "ReplicaSet/" + rs.Name, false
|
||||
return "ReplicaSet/" + rs.Name, true
|
||||
|
||||
case "Deployment":
|
||||
dep, err := client.GetClient().AppsV1().Deployments(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -64,7 +58,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if dep.OwnerReferences != nil {
|
||||
return GetParent(client, dep.ObjectMeta)
|
||||
}
|
||||
return "Deployment/" + dep.Name, false
|
||||
return "Deployment/" + dep.Name, true
|
||||
|
||||
case "StatefulSet":
|
||||
sts, err := client.GetClient().AppsV1().StatefulSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -74,7 +68,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if sts.OwnerReferences != nil {
|
||||
return GetParent(client, sts.ObjectMeta)
|
||||
}
|
||||
return "StatefulSet/" + sts.Name, false
|
||||
return "StatefulSet/" + sts.Name, true
|
||||
|
||||
case "DaemonSet":
|
||||
ds, err := client.GetClient().AppsV1().DaemonSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -84,7 +78,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if ds.OwnerReferences != nil {
|
||||
return GetParent(client, ds.ObjectMeta)
|
||||
}
|
||||
return "DaemonSet/" + ds.Name, false
|
||||
return "DaemonSet/" + ds.Name, true
|
||||
|
||||
case "Ingress":
|
||||
ds, err := client.GetClient().NetworkingV1().Ingresses(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -94,7 +88,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if ds.OwnerReferences != nil {
|
||||
return GetParent(client, ds.ObjectMeta)
|
||||
}
|
||||
return "Ingress/" + ds.Name, false
|
||||
return "Ingress/" + ds.Name, true
|
||||
|
||||
case "MutatingWebhookConfiguration":
|
||||
mw, err := client.GetClient().AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -104,7 +98,7 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if mw.OwnerReferences != nil {
|
||||
return GetParent(client, mw.ObjectMeta)
|
||||
}
|
||||
return "MutatingWebhook/" + mw.Name, false
|
||||
return "MutatingWebhook/" + mw.Name, true
|
||||
|
||||
case "ValidatingWebhookConfiguration":
|
||||
vw, err := client.GetClient().AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
@@ -114,11 +108,11 @@ func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool)
|
||||
if vw.OwnerReferences != nil {
|
||||
return GetParent(client, vw.ObjectMeta)
|
||||
}
|
||||
return "ValidatingWebhook/" + vw.Name, false
|
||||
return "ValidatingWebhook/" + vw.Name, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta.Name, false
|
||||
return "", false
|
||||
}
|
||||
|
||||
func RemoveDuplicates(slice []string) ([]string, []string) {
|
||||
@@ -183,7 +177,8 @@ func GetCacheKey(provider string, language string, sEnc string) string {
|
||||
|
||||
func GetPodListByLabels(client k.Interface,
|
||||
namespace string,
|
||||
labels map[string]string) (*v1.PodList, error) {
|
||||
labels map[string]string,
|
||||
) (*v1.PodList, error) {
|
||||
pods, err := client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{
|
||||
LabelSelector: metav1.FormatLabelSelector(&metav1.LabelSelector{
|
||||
MatchLabels: labels,
|
||||
@@ -207,7 +202,7 @@ func FileExists(path string) (bool, error) {
|
||||
}
|
||||
|
||||
func EnsureDirExists(dir string) error {
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
|
||||
if errors.Is(err, os.ErrExist) {
|
||||
return nil
|
||||
@@ -241,3 +236,78 @@ func LabelsIncludeAny(predefinedSelector, Labels map[string]string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
|
||||
|
||||
// get the list of events
|
||||
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
|
||||
metav1.ListOptions{
|
||||
FieldSelector: "involvedObject.name=" + name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// find most recent event
|
||||
var latestEvent *v1.Event
|
||||
for _, event := range events.Items {
|
||||
if latestEvent == nil {
|
||||
// this is required, as a pointer to a loop variable would always yield the latest value in the range
|
||||
e := event
|
||||
latestEvent = &e
|
||||
}
|
||||
if event.LastTimestamp.After(latestEvent.LastTimestamp.Time) {
|
||||
// this is required, as a pointer to a loop variable would always yield the latest value in the range
|
||||
e := event
|
||||
latestEvent = &e
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -26,29 +26,6 @@ import (
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestSliceContainsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
slice []string
|
||||
s string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
slice: []string{"temp", "value"},
|
||||
s: "value",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.s, func(t *testing.T) {
|
||||
require.Equal(t, tt.expected, SliceContainsString(tt.slice, tt.s))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetParent(t *testing.T) {
|
||||
ownerName := "test-name"
|
||||
namespace := "test"
|
||||
@@ -105,7 +82,7 @@ func TestGetParent(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
kind: "Unknown",
|
||||
expectedOutput: ownerName,
|
||||
expectedOutput: "",
|
||||
},
|
||||
{
|
||||
kind: "ReplicaSet",
|
||||
@@ -178,8 +155,12 @@ func TestGetParent(t *testing.T) {
|
||||
},
|
||||
}
|
||||
output, ok := GetParent(&kubeClient, meta)
|
||||
if meta.OwnerReferences[0].Name != "" {
|
||||
require.Equal(t, true, ok)
|
||||
} else {
|
||||
require.Equal(t, false, ok)
|
||||
}
|
||||
require.Equal(t, tt.expectedOutput, output)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -409,6 +390,7 @@ func TestGetPodListByLabels(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
filePath string
|
||||
|
||||
@@ -12,37 +12,30 @@
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"schedule": [
|
||||
"at any time"
|
||||
],
|
||||
"platformAutomerge": true,
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackageNames": ["^github.com/Azure/azure-sdk-for-go/"],
|
||||
"matchPackageNames": ["azure-sdk-for-go"],
|
||||
"enabled": true,
|
||||
"group": "azure-group"
|
||||
"groupName": "azure-group"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["^github.com/prometheus/"],
|
||||
"matchPackageNames": ["prometheus"],
|
||||
"enabled": true,
|
||||
"group": "prometheus-group"
|
||||
"groupName": "prometheus-group"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["^k8s.io/", "^sigs.k8s.io/"],
|
||||
"matchPackageNames": ["k8s.io", "sigs.k8s.io"],
|
||||
"enabled": true,
|
||||
"groupName": "kubernetes-group"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["^github.com/golang/","^golang.org"],
|
||||
"matchPackageNames": ["golang"],
|
||||
"enabled": true,
|
||||
"group": "golang-group"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["^github.com/"],
|
||||
"enabled": true,
|
||||
"group": "github arbitrary dependencies"
|
||||
},
|
||||
{
|
||||
"description": "Exclude retracted cohere-go versions: https://github.com/renovatebot/renovate/issues/13012",
|
||||
"matchPackageNames": ["github.com/cohere-ai/cohere-go"],
|
||||
"allowedVersions": "<1"
|
||||
"groupName": "golang-group"
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
|
||||
Reference in New Issue
Block a user