mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 19:42:38 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57d32720dc | ||
|
|
0f700f0cd3 | ||
|
|
74fbde0053 | ||
|
|
ff619481cc | ||
|
|
5636515db9 | ||
|
|
47f09ac686 | ||
|
|
be4fb1cc03 | ||
|
|
5947876e49 | ||
|
|
7d4cb26713 | ||
|
|
6b9f346bf6 | ||
|
|
42654e7f55 | ||
|
|
7dfe8bef0f | ||
|
|
dfcc5dc5a1 | ||
|
|
d7cb19ad29 | ||
|
|
306b3c9997 | ||
|
|
1e57b7774c | ||
|
|
d308c511fb | ||
|
|
4faf77d91a | ||
|
|
b2241c03c9 | ||
|
|
0b7ddf5e3b | ||
|
|
d0f03641ae | ||
|
|
e76bdb0c23 | ||
|
|
cae94e7b6d | ||
|
|
7e375a30be |
2
.github/workflows/build_container.yaml
vendored
2
.github/workflows/build_container.yaml
vendored
@@ -96,7 +96,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
context: .
|
||||
file: ./container/Dockerfile
|
||||
|
||||
4
.github/workflows/golangci_lint.yaml
vendored
4
.github/workflows/golangci_lint.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7
|
||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
|
||||
with:
|
||||
version: v2.0
|
||||
version: v2.1.0
|
||||
only-new-issues: true
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
- name: Download Syft
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
password: ${{ secrets.K8SGPT_BOT_SECRET }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
context: .
|
||||
file: ./container/Dockerfile
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
output-file: ./sbom-${{ env.IMAGE_NAME }}.spdx.json
|
||||
|
||||
- name: Attach SBOM to release
|
||||
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2
|
||||
with:
|
||||
tag_name: ${{ needs.release-please.outputs.tag_name }}
|
||||
files: ./sbom-${{ env.IMAGE_NAME }}.spdx.json
|
||||
|
||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@@ -18,13 +18,13 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run test
|
||||
run: go test ./... -coverprofile=coverage.txt
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.4.16"}
|
||||
{".":"0.4.20"}
|
||||
60
CHANGELOG.md
60
CHANGELOG.md
@@ -1,5 +1,65 @@
|
||||
# Changelog
|
||||
|
||||
## [0.4.20](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.19...v0.4.20) (2025-06-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added cache purge ([#1532](https://github.com/k8sgpt-ai/k8sgpt/issues/1532)) ([74fbde0](https://github.com/k8sgpt-ai/k8sgpt/commit/74fbde00537e627c408b317ff9098227be11e2ad))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* model name ([#1535](https://github.com/k8sgpt-ai/k8sgpt/issues/1535)) ([0f700f0](https://github.com/k8sgpt-ai/k8sgpt/commit/0f700f0cd39bf5881d6c05240b842f4df7a6c016))
|
||||
|
||||
## [0.4.19](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.18...v0.4.19) (2025-06-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* fixed haiku ([#1530](https://github.com/k8sgpt-ai/k8sgpt/issues/1530)) ([5636515](https://github.com/k8sgpt-ai/k8sgpt/commit/5636515db98b529689a214af5066d50b5e42d3a1))
|
||||
|
||||
## [0.4.18](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.17...v0.4.18) (2025-06-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to 4c0f3b2 ([#1523](https://github.com/k8sgpt-ai/k8sgpt/issues/1523)) ([7d4cb26](https://github.com/k8sgpt-ai/k8sgpt/commit/7d4cb267130f60088350213482795f37594cb0bc))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1509](https://github.com/k8sgpt-ai/k8sgpt/issues/1509)) ([d7cb19a](https://github.com/k8sgpt-ai/k8sgpt/commit/d7cb19ad29c92eaba552ba723945c937fc3c42da))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update codecov/codecov-action digest to 18283e0 ([#1513](https://github.com/k8sgpt-ai/k8sgpt/issues/1513)) ([42654e7](https://github.com/k8sgpt-ai/k8sgpt/commit/42654e7f55d7a9e9be5b664adaaa8979106e7298))
|
||||
* **deps:** update docker/build-push-action digest to 1dc7386 ([#1512](https://github.com/k8sgpt-ai/k8sgpt/issues/1512)) ([dfcc5dc](https://github.com/k8sgpt-ai/k8sgpt/commit/dfcc5dc5a15a3d59a7f6317944784e3ecd86fb50))
|
||||
* **deps:** update docker/build-push-action digest to 2634353 ([#1517](https://github.com/k8sgpt-ai/k8sgpt/issues/1517)) ([7dfe8be](https://github.com/k8sgpt-ai/k8sgpt/commit/7dfe8bef0face65f607475a6620923fdfed57961))
|
||||
* **deps:** update softprops/action-gh-release digest to 72f2c25 ([#1526](https://github.com/k8sgpt-ai/k8sgpt/issues/1526)) ([5947876](https://github.com/k8sgpt-ai/k8sgpt/commit/5947876e4942729eea883937faf5e2b47d1f16ec))
|
||||
* **deps:** update softprops/action-gh-release digest to d5382d3 ([#1525](https://github.com/k8sgpt-ai/k8sgpt/issues/1525)) ([6b9f346](https://github.com/k8sgpt-ai/k8sgpt/commit/6b9f346bf668ed3517b23b99000611ea14afafe2))
|
||||
* model access ([#1529](https://github.com/k8sgpt-ai/k8sgpt/issues/1529)) ([be4fb1c](https://github.com/k8sgpt-ai/k8sgpt/commit/be4fb1cc034d9c3843cf3e9912a26e05bd54c146))
|
||||
|
||||
## [0.4.17](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.16...v0.4.17) (2025-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adding fixes for Messages API issue 1391 ([#1504](https://github.com/k8sgpt-ai/k8sgpt/issues/1504)) ([b2241c0](https://github.com/k8sgpt-ai/k8sgpt/commit/b2241c03c975aeab02897d73e57cd351f60f3af3))
|
||||
* new job analyzer ([#1506](https://github.com/k8sgpt-ai/k8sgpt/issues/1506)) ([0b7ddf5](https://github.com/k8sgpt-ai/k8sgpt/commit/0b7ddf5e3b93e56ea92dfb6447e97c067cad9e54))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* align documentation to reflect default analyzers properly ([#1498](https://github.com/k8sgpt-ai/k8sgpt/issues/1498)) ([7e375a3](https://github.com/k8sgpt-ai/k8sgpt/commit/7e375a30bee24198f9221e4a4aea17fcd2fe005c))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1454](https://github.com/k8sgpt-ai/k8sgpt/issues/1454)) ([d0f0364](https://github.com/k8sgpt-ai/k8sgpt/commit/d0f03641ae372a00cd0eca1f41ef30a988d436bc))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1500](https://github.com/k8sgpt-ai/k8sgpt/issues/1500)) ([d308c51](https://github.com/k8sgpt-ai/k8sgpt/commit/d308c511fbe06e012c641dfa08c4dcf4181b243a))
|
||||
* panic in k8sgpt auth update ([#1497](https://github.com/k8sgpt-ai/k8sgpt/issues/1497)) ([cae94e7](https://github.com/k8sgpt-ai/k8sgpt/commit/cae94e7b6df1684a3b61af3e7aa0f4e68e8df594))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update actions/setup-go digest to d35c59a ([#1495](https://github.com/k8sgpt-ai/k8sgpt/issues/1495)) ([e76bdb0](https://github.com/k8sgpt-ai/k8sgpt/commit/e76bdb0c23b7d23972d99661c8fe1bffe5f9f398))
|
||||
* **deps:** update golangci/golangci-lint-action action to v8 ([#1490](https://github.com/k8sgpt-ai/k8sgpt/issues/1490)) ([1e57b77](https://github.com/k8sgpt-ai/k8sgpt/commit/1e57b7774c20bda4ae0b0d765278bcd3504cfb33))
|
||||
* golangci lint ([#1508](https://github.com/k8sgpt-ai/k8sgpt/issues/1508)) ([4faf77d](https://github.com/k8sgpt-ai/k8sgpt/commit/4faf77d91a3da8fdd6166ec1c381a151e5846057))
|
||||
|
||||
## [0.4.16](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.15...v0.4.16) (2025-05-06)
|
||||
|
||||
|
||||
|
||||
15
README.md
15
README.md
@@ -62,7 +62,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_386.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
|
||||
@@ -70,7 +70,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
</details>
|
||||
@@ -83,7 +83,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
|
||||
@@ -94,7 +94,7 @@ sudo dpkg -i k8sgpt_386.deb
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
|
||||
@@ -109,7 +109,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_386.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_386.apk
|
||||
apk add --allow-untrusted k8sgpt_386.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -118,7 +118,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.16/k8sgpt_amd64.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.20/k8sgpt_amd64.apk
|
||||
apk add --allow-untrusted k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -252,10 +252,12 @@ you will be able to write your own analyzers.
|
||||
- [x] ingressAnalyzer
|
||||
- [x] statefulSetAnalyzer
|
||||
- [x] deploymentAnalyzer
|
||||
- [x] jobAnalyzer
|
||||
- [x] cronJobAnalyzer
|
||||
- [x] nodeAnalyzer
|
||||
- [x] mutatingWebhookAnalyzer
|
||||
- [x] validatingWebhookAnalyzer
|
||||
- [x] configMapAnalyzer
|
||||
|
||||
#### Optional
|
||||
|
||||
@@ -268,7 +270,6 @@ you will be able to write your own analyzers.
|
||||
- [x] logAnalyzer
|
||||
- [x] storageAnalyzer
|
||||
- [x] securityAnalyzer
|
||||
- [x] configMapAnalyzer
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ var updateCmd = &cobra.Command{
|
||||
}
|
||||
}
|
||||
if !foundBackend {
|
||||
color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", args[0])
|
||||
color.Red("Error: %s does not exist in configuration file. Please use k8sgpt auth new.", backend)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
43
cmd/cache/purge.go
vendored
43
cmd/cache/purge.go
vendored
@@ -23,23 +23,51 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var all bool
|
||||
|
||||
var purgeCmd = &cobra.Command{
|
||||
Use: "purge [object name]",
|
||||
Short: "Purge a remote cache",
|
||||
Long: "This command allows you to delete/purge one object from the cache",
|
||||
Long: "This command allows you to delete/purge one object from the cache or all objects with --all flag.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
color.Red("Error: Please provide a value for object name. Run k8sgpt cache purge --help")
|
||||
os.Exit(1)
|
||||
}
|
||||
objectKey := args[0]
|
||||
fmt.Println(color.YellowString("Purging a remote cache."))
|
||||
c, err := cache.GetCacheConfiguration()
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if all {
|
||||
fmt.Println(color.YellowString("Purging all objects from the remote cache."))
|
||||
names, err := c.List()
|
||||
if err != nil {
|
||||
color.Red("Error listing cache objects: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(names) == 0 {
|
||||
fmt.Println(color.GreenString("No objects to delete."))
|
||||
return
|
||||
}
|
||||
var failed []string
|
||||
for _, obj := range names {
|
||||
err := c.Remove(obj.Name)
|
||||
if err != nil {
|
||||
failed = append(failed, obj.Name)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
color.Red("Failed to delete: %v", failed)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(color.GreenString("All objects deleted."))
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
color.Red("Error: Please provide a value for object name or use --all. Run k8sgpt cache purge --help")
|
||||
os.Exit(1)
|
||||
}
|
||||
objectKey := args[0]
|
||||
fmt.Println(color.YellowString("Purging a remote cache."))
|
||||
err = c.Remove(objectKey)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
@@ -50,5 +78,6 @@ var purgeCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
purgeCmd.Flags().BoolVar(&all, "all", false, "Purge all objects in the cache")
|
||||
CacheCmd.AddCommand(purgeCmd)
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -284,7 +284,7 @@ require (
|
||||
k8s.io/component-base v0.32.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
oras.land/oras-go v1.2.5 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.18.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -2256,8 +2256,8 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJ
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||
k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
|
||||
k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=
|
||||
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg=
|
||||
k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3 h1:uUSDGlOIkdPT4svjlhi+JEnP2Ufw7AM/F5QDYiEL02U=
|
||||
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3/go.mod h1:FeMbTLlxQqSASwlRCrYEOsZ0OKUgSj52qxhECwYCJsw=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
|
||||
@@ -58,6 +58,7 @@ var BEDROCKER_SUPPORTED_REGION = []string{
|
||||
}
|
||||
|
||||
var defaultModels = []bedrock_support.BedrockModel{
|
||||
|
||||
{
|
||||
Name: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
@@ -84,8 +85,8 @@ var defaultModels = []bedrock_support.BedrockModel{
|
||||
},
|
||||
{
|
||||
Name: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
Completion: &bedrock_support.CohereCompletion{},
|
||||
Response: &bedrock_support.CohereResponse{},
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
@@ -96,8 +97,8 @@ var defaultModels = []bedrock_support.BedrockModel{
|
||||
},
|
||||
{
|
||||
Name: "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
Completion: &bedrock_support.CohereCompletion{},
|
||||
Response: &bedrock_support.CohereResponse{},
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
@@ -255,13 +256,14 @@ var defaultModels = []bedrock_support.BedrockModel{
|
||||
},
|
||||
{
|
||||
Name: "anthropic.claude-3-haiku-20240307-v1:0",
|
||||
Completion: &bedrock_support.CohereCompletion{},
|
||||
Response: &bedrock_support.CohereResponse{},
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "anthropic.claude-3-haiku-20240307-v1:0",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -311,7 +313,6 @@ func (a *AmazonBedRockClient) getModelFromString(model string) (*bedrock_support
|
||||
|
||||
// Trim spaces from the model name
|
||||
model = strings.TrimSpace(model)
|
||||
modelLower := strings.ToLower(model)
|
||||
|
||||
// Try to find an exact match first
|
||||
for i := range a.models {
|
||||
@@ -322,26 +323,27 @@ func (a *AmazonBedRockClient) getModelFromString(model string) (*bedrock_support
|
||||
}
|
||||
}
|
||||
|
||||
// If no exact match, try partial match
|
||||
for i := range a.models {
|
||||
modelNameLower := strings.ToLower(a.models[i].Name)
|
||||
modelConfigNameLower := strings.ToLower(a.models[i].Config.ModelName)
|
||||
|
||||
// Check if the input string contains the model name or vice versa
|
||||
if strings.Contains(modelNameLower, modelLower) || strings.Contains(modelLower, modelNameLower) ||
|
||||
strings.Contains(modelConfigNameLower, modelLower) || strings.Contains(modelLower, modelConfigNameLower) {
|
||||
// Create a copy to avoid returning a pointer to a loop variable
|
||||
modelCopy := a.models[i]
|
||||
// for partial match, set the model name to the input string if it is a valid ARN
|
||||
if validateModelArn(modelLower) {
|
||||
modelCopy.Config.ModelName = modelLower
|
||||
}
|
||||
|
||||
return &modelCopy, nil
|
||||
}
|
||||
supportedModels := make([]string, len(a.models))
|
||||
for i, m := range a.models {
|
||||
supportedModels[i] = m.Name
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("model '%s' not found in supported models", model)
|
||||
supportedRegions := BEDROCKER_SUPPORTED_REGION
|
||||
|
||||
// Pretty-print supported models and regions
|
||||
modelList := ""
|
||||
for _, m := range supportedModels {
|
||||
modelList += " - " + m + "\n"
|
||||
}
|
||||
regionList := ""
|
||||
for _, r := range supportedRegions {
|
||||
regionList += " - " + r + "\n"
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(
|
||||
"model '%s' not found in supported models.\n\nSupported models:\n%sSupported regions:\n%s",
|
||||
model, modelList, regionList,
|
||||
)
|
||||
}
|
||||
|
||||
// Configure configures the AmazonBedRockClient with the provided configuration.
|
||||
@@ -353,10 +355,10 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
|
||||
// Get the model input
|
||||
modelInput := config.GetModel()
|
||||
|
||||
|
||||
// Determine the appropriate region to use
|
||||
var region string
|
||||
|
||||
|
||||
// Check if the model input is actually an inference profile ARN
|
||||
if validateInferenceProfileArn(modelInput) {
|
||||
// Extract the region from the inference profile ARN
|
||||
@@ -370,14 +372,17 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
// Use the provided region or default
|
||||
region = GetRegionOrDefault(config.GetProviderRegion())
|
||||
}
|
||||
|
||||
|
||||
// Only create AWS clients if they haven't been injected (for testing)
|
||||
if a.client == nil || a.mgmtClient == nil {
|
||||
// Create a new AWS config with the determined region
|
||||
cfg, err := awsconfig.LoadDefaultConfig(context.Background(),
|
||||
cfg, err := awsconfig.LoadDefaultConfig(context.Background(),
|
||||
awsconfig.WithRegion(region),
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "InvalidAccessKeyId") || strings.Contains(err.Error(), "SignatureDoesNotMatch") || strings.Contains(err.Error(), "NoCredentialProviders") {
|
||||
return fmt.Errorf("AWS credentials are invalid or missing. Please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config. Details: %v", err)
|
||||
}
|
||||
return fmt.Errorf("failed to load AWS config for region %s: %w", region, err)
|
||||
}
|
||||
|
||||
@@ -385,7 +390,7 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
a.client = bedrockruntime.NewFromConfig(cfg)
|
||||
a.mgmtClient = bedrock.NewFromConfig(cfg)
|
||||
}
|
||||
|
||||
|
||||
// Handle model selection based on input type
|
||||
if validateInferenceProfileArn(modelInput) {
|
||||
// Get the inference profile details
|
||||
@@ -399,7 +404,7 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract model ID from inference profile: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Find the model configuration for the extracted model ID
|
||||
foundModel, err := a.getModelFromString(modelID)
|
||||
if err != nil {
|
||||
@@ -407,7 +412,7 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
return fmt.Errorf("failed to find model configuration for %s: %v", modelID, err)
|
||||
}
|
||||
a.model = foundModel
|
||||
|
||||
|
||||
// Use the inference profile ARN as the model ID for API calls
|
||||
a.model.Config.ModelName = modelInput
|
||||
}
|
||||
@@ -415,12 +420,12 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
// Regular model ID provided
|
||||
foundModel, err := a.getModelFromString(modelInput)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("model '%s' is not supported: %v", modelInput, err)
|
||||
}
|
||||
a.model = foundModel
|
||||
a.model.Config.ModelName = foundModel.Config.ModelName
|
||||
}
|
||||
|
||||
|
||||
// Set common configuration parameters
|
||||
a.temperature = config.GetTemperature()
|
||||
a.topP = config.GetTopP()
|
||||
@@ -438,20 +443,20 @@ func (a *AmazonBedRockClient) getInferenceProfile(ctx context.Context, inference
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid inference profile ARN format: %s", inferenceProfileARN)
|
||||
}
|
||||
|
||||
|
||||
profileID := parts[1]
|
||||
|
||||
|
||||
// Create the input for the GetInferenceProfile API call
|
||||
input := &bedrock.GetInferenceProfileInput{
|
||||
InferenceProfileIdentifier: aws.String(profileID),
|
||||
}
|
||||
|
||||
|
||||
// Call the GetInferenceProfile API
|
||||
output, err := a.mgmtClient.GetInferenceProfile(ctx, input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get inference profile: %w", err)
|
||||
}
|
||||
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -460,25 +465,25 @@ func (a *AmazonBedRockClient) extractModelFromInferenceProfile(profile *bedrock.
|
||||
if profile == nil || len(profile.Models) == 0 {
|
||||
return "", fmt.Errorf("inference profile does not contain any models")
|
||||
}
|
||||
|
||||
|
||||
// Check if the first model has a non-nil ModelArn
|
||||
if profile.Models[0].ModelArn == nil {
|
||||
return "", fmt.Errorf("model information is missing in inference profile")
|
||||
}
|
||||
|
||||
|
||||
// Get the first model ARN from the profile
|
||||
modelARN := aws.ToString(profile.Models[0].ModelArn)
|
||||
if modelARN == "" {
|
||||
return "", fmt.Errorf("model ARN is empty in inference profile")
|
||||
}
|
||||
|
||||
|
||||
// Extract the model ID from the ARN
|
||||
// ARN format: arn:aws:bedrock:region::foundation-model/model-id
|
||||
parts := strings.Split(modelARN, "/")
|
||||
if len(parts) != 2 {
|
||||
return "", fmt.Errorf("invalid model ARN format: %s", modelARN)
|
||||
}
|
||||
|
||||
|
||||
modelID := parts[1]
|
||||
return modelID, nil
|
||||
}
|
||||
@@ -490,11 +495,26 @@ func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string)
|
||||
a.model.Config.Temperature = a.temperature
|
||||
a.model.Config.TopP = a.topP
|
||||
|
||||
supportedModels := make([]string, len(a.models))
|
||||
for i, m := range a.models {
|
||||
supportedModels[i] = m.Name
|
||||
}
|
||||
|
||||
if !bedrock_support.IsModelSupported(a.model.Config.ModelName, supportedModels) {
|
||||
return "", fmt.Errorf("model '%s' is not supported.\nSupported models:\n%s", a.model.Config.ModelName, func() string {
|
||||
s := ""
|
||||
for _, m := range supportedModels {
|
||||
s += " - " + m + "\n"
|
||||
}
|
||||
return s
|
||||
}())
|
||||
}
|
||||
|
||||
body, err := a.model.Completion.GetCompletion(ctx, prompt, a.model.Config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
// Build the parameters for the model invocation
|
||||
params := &bedrockruntime.InvokeModelInput{
|
||||
Body: body,
|
||||
@@ -502,10 +522,13 @@ func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string)
|
||||
ContentType: aws.String("application/json"),
|
||||
Accept: aws.String("application/json"),
|
||||
}
|
||||
|
||||
|
||||
// Invoke the model
|
||||
resp, err := a.client.InvokeModel(ctx, params)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "InvalidAccessKeyId") || strings.Contains(err.Error(), "SignatureDoesNotMatch") || strings.Contains(err.Error(), "NoCredentialProviders") {
|
||||
return "", fmt.Errorf("AWS credentials are invalid or missing. Please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config. Details: %v", err)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -47,57 +47,54 @@ var testModels = []bedrock_support.BedrockModel{
|
||||
func TestBedrockModelConfig(t *testing.T) {
|
||||
client := &AmazonBedRockClient{models: testModels}
|
||||
|
||||
foundModel, err := client.getModelFromString("arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
assert.Nil(t, err, "Error should be nil")
|
||||
assert.Equal(t, foundModel.Config.MaxTokens, 100)
|
||||
assert.Equal(t, foundModel.Config.Temperature, float32(0.5))
|
||||
assert.Equal(t, foundModel.Config.TopP, float32(0.9))
|
||||
assert.Equal(t, foundModel.Config.ModelName, "arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
// Should return error for ARN input (no exact match)
|
||||
_, err := client.getModelFromString("arn:aws:bedrock:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
assert.NotNil(t, err, "Should return error for ARN input")
|
||||
}
|
||||
|
||||
func TestBedrockInvalidModel(t *testing.T) {
|
||||
client := &AmazonBedRockClient{models: testModels}
|
||||
|
||||
foundModel, err := client.getModelFromString("arn:aws:s3:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
assert.Nil(t, err, "Error should be nil")
|
||||
assert.Equal(t, foundModel.Config.MaxTokens, 100)
|
||||
// Should return error for invalid model name
|
||||
_, err := client.getModelFromString("arn:aws:s3:us-east-1:*:inference-policy/anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
assert.NotNil(t, err, "Should return error for invalid model name")
|
||||
}
|
||||
|
||||
func TestBedrockInferenceProfileARN(t *testing.T) {
|
||||
// Create a mock client with test models
|
||||
client := &AmazonBedRockClient{models: testModels}
|
||||
|
||||
|
||||
// Test with a valid inference profile ARN
|
||||
inferenceProfileARN := "arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile"
|
||||
config := AIProvider{
|
||||
Model: inferenceProfileARN,
|
||||
ProviderRegion: "us-east-1",
|
||||
}
|
||||
|
||||
|
||||
// This will fail in a real environment without mocks, but we're just testing the validation logic
|
||||
err := client.Configure(&config)
|
||||
// We expect an error since we can't actually call AWS in tests
|
||||
assert.NotNil(t, err, "Error should not be nil without AWS mocks")
|
||||
|
||||
|
||||
// Test with a valid application inference profile ARN
|
||||
appInferenceProfileARN := "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile"
|
||||
config = AIProvider{
|
||||
Model: appInferenceProfileARN,
|
||||
ProviderRegion: "us-east-1",
|
||||
}
|
||||
|
||||
|
||||
// This will fail in a real environment without mocks, but we're just testing the validation logic
|
||||
err = client.Configure(&config)
|
||||
// We expect an error since we can't actually call AWS in tests
|
||||
assert.NotNil(t, err, "Error should not be nil without AWS mocks")
|
||||
|
||||
|
||||
// Test with an invalid inference profile ARN format
|
||||
invalidARN := "arn:aws:bedrock:us-east-1:123456789012:invalid-resource/my-profile"
|
||||
config = AIProvider{
|
||||
Model: invalidARN,
|
||||
ProviderRegion: "us-east-1",
|
||||
}
|
||||
|
||||
|
||||
err = client.Configure(&config)
|
||||
assert.NotNil(t, err, "Error should not be nil for invalid inference profile ARN format")
|
||||
}
|
||||
@@ -146,7 +143,7 @@ func TestGetModelFromString(t *testing.T) {
|
||||
name: "partial model name match",
|
||||
model: "claude-3-5-sonnet",
|
||||
wantModel: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
wantErr: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "model name with different version",
|
||||
|
||||
@@ -7,24 +7,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var SUPPPORTED_BEDROCK_MODELS = []string{
|
||||
"anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
"us.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-v2",
|
||||
"anthropic.claude-v1",
|
||||
"anthropic.claude-instant-v1",
|
||||
"ai21.j2-ultra-v1",
|
||||
"ai21.j2-jumbo-instruct",
|
||||
"amazon.titan-text-express-v1",
|
||||
"amazon.nova-pro-v1:0",
|
||||
"eu.amazon.nova-pro-v1:0",
|
||||
"us.amazon.nova-pro-v1:0",
|
||||
"amazon.nova-lite-v1:0",
|
||||
"eu.amazon.nova-lite-v1:0",
|
||||
"us.amazon.nova-lite-v1:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
}
|
||||
|
||||
type ICompletion interface {
|
||||
GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error)
|
||||
}
|
||||
@@ -94,18 +76,20 @@ type AmazonCompletion struct {
|
||||
completion ICompletion
|
||||
}
|
||||
|
||||
func isModelSupported(modelName string) bool {
|
||||
for _, supportedModel := range SUPPPORTED_BEDROCK_MODELS {
|
||||
if strings.Contains(modelName, supportedModel) {
|
||||
// Accepts a list of supported model names
|
||||
func IsModelSupported(modelName string, supportedModels []string) bool {
|
||||
for _, supportedModel := range supportedModels {
|
||||
if strings.EqualFold(modelName, supportedModel) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Note: The caller should check model support before calling GetCompletion.
|
||||
func (a *AmazonCompletion) GetCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {
|
||||
if !isModelSupported(modelConfig.ModelName) {
|
||||
return nil, fmt.Errorf("model %s is not supported", modelConfig.ModelName)
|
||||
if a == nil || modelConfig.ModelName == "" {
|
||||
return nil, fmt.Errorf("no model name provided to Bedrock completion")
|
||||
}
|
||||
if strings.Contains(modelConfig.ModelName, "nova") {
|
||||
return a.GetNovaCompletion(ctx, prompt, modelConfig)
|
||||
@@ -128,7 +112,6 @@ func (a *AmazonCompletion) GetDefaultCompletion(ctx context.Context, prompt stri
|
||||
return []byte{}, err
|
||||
}
|
||||
return body, nil
|
||||
|
||||
}
|
||||
|
||||
func (a *AmazonCompletion) GetNovaCompletion(ctx context.Context, prompt string, modelConfig BedrockModelConfig) ([]byte, error) {
|
||||
|
||||
@@ -158,21 +158,6 @@ func TestAmazonCompletion_GetCompletion_Default(t *testing.T) {
|
||||
assert.Equal(t, 0.7, textConfig["topP"])
|
||||
}
|
||||
|
||||
func TestAmazonCompletion_GetCompletion_UnsupportedModel(t *testing.T) {
|
||||
completion := &AmazonCompletion{}
|
||||
modelConfig := BedrockModelConfig{
|
||||
MaxTokens: 200,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.7,
|
||||
ModelName: "unsupported-model",
|
||||
}
|
||||
prompt := "Test prompt"
|
||||
|
||||
_, err := completion.GetCompletion(context.Background(), prompt, modelConfig)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "model unsupported-model is not supported")
|
||||
}
|
||||
|
||||
func TestAmazonCompletion_GetCompletion_Inference_Profile(t *testing.T) {
|
||||
completion := &AmazonCompletion{}
|
||||
modelConfig := BedrockModelConfig{
|
||||
@@ -187,7 +172,11 @@ func TestAmazonCompletion_GetCompletion_Inference_Profile(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_isModelSupported(t *testing.T) {
|
||||
assert.True(t, isModelSupported("anthropic.claude-v2"))
|
||||
assert.False(t, isModelSupported("unsupported-model"))
|
||||
func TestIsModelSupported(t *testing.T) {
|
||||
supported := []string{
|
||||
"anthropic.claude-v2",
|
||||
"anthropic.claude-v1",
|
||||
}
|
||||
assert.True(t, IsModelSupported("anthropic.claude-v2", supported))
|
||||
assert.False(t, IsModelSupported("unsupported-model", supported))
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ var coreAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"Service": ServiceAnalyzer{},
|
||||
"Ingress": IngressAnalyzer{},
|
||||
"StatefulSet": StatefulSetAnalyzer{},
|
||||
"Job": JobAnalyzer{},
|
||||
"CronJob": CronJobAnalyzer{},
|
||||
"Node": NodeAnalyzer{},
|
||||
"ValidatingWebhookConfiguration": ValidatingWebhookAnalyzer{},
|
||||
|
||||
107
pkg/analyzer/job.go
Normal file
107
pkg/analyzer/job.go
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2025 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type JobAnalyzer struct{}
|
||||
|
||||
func (analyzer JobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "Job"
|
||||
apiDoc := kubernetes.K8sApiReference{
|
||||
Kind: kind,
|
||||
ApiVersion: schema.GroupVersion{
|
||||
Group: "batch",
|
||||
Version: "v1",
|
||||
},
|
||||
OpenapiSchema: a.OpenapiSchema,
|
||||
}
|
||||
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
JobList, err := a.Client.GetClient().BatchV1().Jobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, Job := range JobList.Items {
|
||||
var failures []common.Failure
|
||||
if Job.Spec.Suspend != nil && *Job.Spec.Suspend {
|
||||
doc := apiDoc.GetApiDocV2("spec.suspend")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Job %s is suspended", Job.Name),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: Job.Namespace,
|
||||
Masked: util.MaskString(Job.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: Job.Name,
|
||||
Masked: util.MaskString(Job.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if Job.Status.Failed > 0 {
|
||||
doc := apiDoc.GetApiDocV2("status.failed")
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Job %s has failed", Job.Name),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: Job.Namespace,
|
||||
Masked: util.MaskString(Job.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: Job.Name,
|
||||
Masked: util.MaskString(Job.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", Job.Namespace, Job.Name)] = common.PreAnalysis{
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, Job.Name, Job.Namespace).Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
currentAnalysis := common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
215
pkg/analyzer/job_test.go
Normal file
215
pkg/analyzer/job_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
Copyright 2025 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"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestJobAnalyzer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config common.Analyzer
|
||||
expectations []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "Suspended Job",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "suspended-job",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Suspend: boolPtr(true),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/suspended-job",
|
||||
failuresCount: 1, // One failure for being suspended
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Failed Job",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "failed-job",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.JobSpec{},
|
||||
Status: batchv1.JobStatus{
|
||||
Failed: 1,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/failed-job",
|
||||
failuresCount: 1, // One failure for failed job
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid Job",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-job",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.JobSpec{},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
// No expectations for valid job
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiple issues",
|
||||
config: common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "multiple-issues",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Suspend: boolPtr(true),
|
||||
},
|
||||
Status: batchv1.JobStatus{
|
||||
Failed: 1,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
},
|
||||
expectations: []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/multiple-issues",
|
||||
failuresCount: 2, // Two failures: suspended and failed job
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
analyzer := JobAnalyzer{}
|
||||
results, err := analyzer.Analyze(tt.config)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, len(tt.expectations))
|
||||
|
||||
// Sort results by name for consistent comparison
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
for i, expectation := range tt.expectations {
|
||||
require.Equal(t, expectation.name, results[i].Name)
|
||||
require.Len(t, results[i].Error, expectation.failuresCount)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobAnalyzerLabelSelector(t *testing.T) {
|
||||
clientSet := fake.NewSimpleClientset(
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "job-with-label",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{},
|
||||
Status: batchv1.JobStatus{
|
||||
Failed: 1,
|
||||
},
|
||||
},
|
||||
&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "job-without-label",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.JobSpec{},
|
||||
},
|
||||
)
|
||||
|
||||
// Test with label selector
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientSet,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
LabelSelector: "app=test",
|
||||
}
|
||||
|
||||
analyzer := JobAnalyzer{}
|
||||
results, err := analyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "default/job-with-label", results[0].Name)
|
||||
}
|
||||
@@ -46,15 +46,17 @@ func (NodeAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
// https://kubernetes.io/docs/concepts/architecture/nodes/#condition
|
||||
switch nodeCondition.Type {
|
||||
case v1.NodeReady:
|
||||
if nodeCondition.Status == v1.ConditionTrue {
|
||||
break
|
||||
if nodeCondition.Status != v1.ConditionTrue {
|
||||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
|
||||
}
|
||||
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 {
|
||||
// For other conditions:
|
||||
// - Report True or Unknown status as failures (for standard conditions)
|
||||
// - Report any unknown condition type as a failure
|
||||
if nodeCondition.Status == v1.ConditionTrue || nodeCondition.Status == v1.ConditionUnknown || !isKnownNodeConditionType(nodeCondition.Type) {
|
||||
failures = addNodeConditionFailure(failures, node.Name, nodeCondition)
|
||||
}
|
||||
}
|
||||
@@ -99,3 +101,17 @@ func addNodeConditionFailure(failures []common.Failure, nodeName string, nodeCon
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
// isKnownNodeConditionType checks if the condition type is a standard Kubernetes node condition
|
||||
func isKnownNodeConditionType(conditionType v1.NodeConditionType) bool {
|
||||
switch conditionType {
|
||||
case v1.NodeReady,
|
||||
v1.NodeMemoryPressure,
|
||||
v1.NodeDiskPressure,
|
||||
v1.NodePIDPressure,
|
||||
v1.NodeNetworkUnavailable:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
18
pkg/cache/s3_based.go
vendored
18
pkg/cache/s3_based.go
vendored
@@ -3,8 +3,9 @@ package cache
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@@ -27,16 +28,19 @@ type S3CacheConfiguration struct {
|
||||
|
||||
func (s *S3Cache) Configure(cacheInfo CacheProvider) error {
|
||||
if cacheInfo.S3.BucketName == "" {
|
||||
log.Fatal("Bucket name not configured")
|
||||
return errors.New("bucket name not configured")
|
||||
}
|
||||
s.bucketName = cacheInfo.S3.BucketName
|
||||
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
sess, err := session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: aws.Config{
|
||||
Region: aws.String(cacheInfo.S3.Region),
|
||||
},
|
||||
}))
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("failed to create AWS session; please check your AWS credentials and configuration: " + err.Error())
|
||||
}
|
||||
if cacheInfo.S3.Endpoint != "" {
|
||||
sess.Config.Endpoint = &cacheInfo.S3.Endpoint
|
||||
sess.Config.S3ForcePathStyle = aws.Bool(true)
|
||||
@@ -50,10 +54,14 @@ func (s *S3Cache) Configure(cacheInfo CacheProvider) error {
|
||||
s3Client := s3.New(sess)
|
||||
|
||||
// Check if the bucket exists, if not create it
|
||||
_, err := s3Client.HeadBucket(&s3.HeadBucketInput{
|
||||
_, err = s3Client.HeadBucket(&s3.HeadBucketInput{
|
||||
Bucket: aws.String(cacheInfo.S3.BucketName),
|
||||
})
|
||||
if err != nil {
|
||||
// Check for AWS credentials error
|
||||
if strings.Contains(err.Error(), "InvalidAccessKeyId") || strings.Contains(err.Error(), "SignatureDoesNotMatch") || strings.Contains(err.Error(), "NoCredentialProviders") {
|
||||
return errors.New("aws credentials are invalid or missing; please check your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables or AWS config")
|
||||
}
|
||||
_, err = s3Client.CreateBucket(&s3.CreateBucketInput{
|
||||
Bucket: aws.String(cacheInfo.S3.BucketName),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user