mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 03:23:47 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c99dc934 | ||
|
|
a821814125 | ||
|
|
50d5d78c06 | ||
|
|
5b4224951e | ||
|
|
290a4be210 | ||
|
|
fe4608793d | ||
|
|
3a1187ad5a | ||
|
|
1819e6f410 | ||
|
|
392c79d0be | ||
|
|
00c07999e2 | ||
|
|
8002d94345 | ||
|
|
0c917fc601 | ||
|
|
08f2855a4d | ||
|
|
57d32720dc | ||
|
|
0f700f0cd3 | ||
|
|
74fbde0053 | ||
|
|
ff619481cc | ||
|
|
5636515db9 | ||
|
|
47f09ac686 | ||
|
|
be4fb1cc03 | ||
|
|
5947876e49 | ||
|
|
7d4cb26713 | ||
|
|
6b9f346bf6 | ||
|
|
42654e7f55 | ||
|
|
7dfe8bef0f | ||
|
|
dfcc5dc5a1 | ||
|
|
d7cb19ad29 | ||
|
|
306b3c9997 | ||
|
|
1e57b7774c | ||
|
|
d308c511fb | ||
|
|
4faf77d91a | ||
|
|
b2241c03c9 | ||
|
|
0b7ddf5e3b | ||
|
|
d0f03641ae | ||
|
|
e76bdb0c23 | ||
|
|
cae94e7b6d | ||
|
|
7e375a30be | ||
|
|
34ff645fa0 | ||
|
|
61b60d5768 | ||
|
|
6a81d2c140 | ||
|
|
21bc76e5b7 | ||
|
|
d5341f3c00 | ||
|
|
752a16c407 | ||
|
|
81da402d46 |
4
.github/workflows/build_container.yaml
vendored
4
.github/workflows/build_container.yaml
vendored
@@ -93,10 +93,10 @@ jobs:
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # 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@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7
|
||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
|
||||
with:
|
||||
version: v2.0
|
||||
version: v2.1.0
|
||||
only-new-issues: true
|
||||
8
.github/workflows/release.yaml
vendored
8
.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
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
password: ${{ secrets.K8SGPT_BOT_SECRET }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # 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.15"}
|
||||
{".":"0.4.23"}
|
||||
120
CHANGELOG.md
120
CHANGELOG.md
@@ -1,5 +1,125 @@
|
||||
# Changelog
|
||||
|
||||
## [0.4.23](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.22...v0.4.23) (2025-08-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ClusterCatalog and ClusterExtension analyzers ([#1555](https://github.com/k8sgpt-ai/k8sgpt/issues/1555)) ([a821814](https://github.com/k8sgpt-ai/k8sgpt/commit/a821814125e25c062ff2faebf9df1b880414c22c))
|
||||
* oci genai chat models ([#1337](https://github.com/k8sgpt-ai/k8sgpt/issues/1337)) ([290a4be](https://github.com/k8sgpt-ai/k8sgpt/commit/290a4be210fbb508214070c31218138781d96142))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1537](https://github.com/k8sgpt-ai/k8sgpt/issues/1537)) ([50d5d78](https://github.com/k8sgpt-ai/k8sgpt/commit/50d5d78c06e42d75a2448989528e5e6be12ea825))
|
||||
* **deps:** update module helm.sh/helm/v3 to v3.17.4 [security] ([#1541](https://github.com/k8sgpt-ai/k8sgpt/issues/1541)) ([5b42249](https://github.com/k8sgpt-ai/k8sgpt/commit/5b4224951e7348e9d78292dadc9b9786957117f1))
|
||||
|
||||
## [0.4.22](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.21...v0.4.22) (2025-07-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add APAC region Claude models support for Amazon Bedrock ([#1543](https://github.com/k8sgpt-ai/k8sgpt/issues/1543)) ([1819e6f](https://github.com/k8sgpt-ai/k8sgpt/commit/1819e6f410d078fce2bda8bbdb22054dfb4fc092))
|
||||
* add streamable-http support for MCP server ([#1546](https://github.com/k8sgpt-ai/k8sgpt/issues/1546)) ([3a1187a](https://github.com/k8sgpt-ai/k8sgpt/commit/3a1187ad5a190713b9216cf6d9d52d54cdb3e4da))
|
||||
|
||||
## [0.4.21](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.20...v0.4.21) (2025-06-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add latest and legacy stable models ([#1539](https://github.com/k8sgpt-ai/k8sgpt/issues/1539)) ([00c0799](https://github.com/k8sgpt-ai/k8sgpt/commit/00c07999e2290e70a6ecb95b255b4924f55ecd5f))
|
||||
* support for claude4 && model names listed ([#1540](https://github.com/k8sgpt-ai/k8sgpt/issues/1540)) ([8002d94](https://github.com/k8sgpt-ai/k8sgpt/commit/8002d943453aac8c3675d7072b25dfdc3aec1c1d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1511](https://github.com/k8sgpt-ai/k8sgpt/issues/1511)) ([08f2855](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2855a4d7e61f3422cb68b0966272a85f617a5))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update docker/setup-buildx-action digest to e468171 ([#1527](https://github.com/k8sgpt-ai/k8sgpt/issues/1527)) ([0c917fc](https://github.com/k8sgpt-ai/k8sgpt/commit/0c917fc60115ef0dc775e858a55964382b20c5e1))
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for Amazon Bedrock Inference Profiles ([#1492](https://github.com/k8sgpt-ai/k8sgpt/issues/1492)) ([21bc76e](https://github.com/k8sgpt-ai/k8sgpt/commit/21bc76e5b77524b48f09ef6707204742dcd879a7))
|
||||
* enhancement of deployment analyzer ([#1406](https://github.com/k8sgpt-ai/k8sgpt/issues/1406)) ([61b60d5](https://github.com/k8sgpt-ai/k8sgpt/commit/61b60d5768b54f98232dcc415e89aa38987dc6e3))
|
||||
* supported regions govcloud ([#1483](https://github.com/k8sgpt-ai/k8sgpt/issues/1483)) ([752a16c](https://github.com/k8sgpt-ai/k8sgpt/commit/752a16c40728f42f10ab6c3177cb7e24f44db339))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to 0f33e8f ([#1484](https://github.com/k8sgpt-ai/k8sgpt/issues/1484)) ([6a81d2c](https://github.com/k8sgpt-ai/k8sgpt/commit/6a81d2c140f00a405b651d6c6dae5e343ffddb4f))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update docker/build-push-action digest to 14487ce ([#1472](https://github.com/k8sgpt-ai/k8sgpt/issues/1472)) ([81da402](https://github.com/k8sgpt-ai/k8sgpt/commit/81da402d46e1a1db83a41b717dfb23eb07d2e919))
|
||||
* **deps:** update golangci/golangci-lint-action digest to 9fae48a ([#1489](https://github.com/k8sgpt-ai/k8sgpt/issues/1489)) ([d5341f3](https://github.com/k8sgpt-ai/k8sgpt/commit/d5341f3c0019c1114254ac05f00c743a0354ec0b))
|
||||
|
||||
## [0.4.15](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.14...v0.4.15) (2025-04-29)
|
||||
|
||||
|
||||
|
||||
33
README.md
33
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.15/k8sgpt_386.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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.15/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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.15/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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.15/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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.15/k8sgpt_386.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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.15/k8sgpt_amd64.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.23/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,8 @@ you will be able to write your own analyzers.
|
||||
- [x] logAnalyzer
|
||||
- [x] storageAnalyzer
|
||||
- [x] securityAnalyzer
|
||||
- [x] configMapAnalyzer
|
||||
- [x] ClusterCatalog
|
||||
- [x] ClusterExtension
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -466,6 +469,22 @@ k8sgpt auth default -p azureopenai
|
||||
Default provider set to azureopenai
|
||||
```
|
||||
|
||||
_Using Amazon Bedrock with inference profiles_
|
||||
|
||||
_System Inference Profile_
|
||||
|
||||
```
|
||||
k8sgpt auth add --backend amazonbedrock --providerRegion us-east-1 --model arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-inference-profile
|
||||
|
||||
```
|
||||
|
||||
_Application Inference Profile_
|
||||
|
||||
```
|
||||
k8sgpt auth add --backend amazonbedrock --providerRegion us-east-1 --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/2uzp4s0w39t6
|
||||
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
<details>
|
||||
|
||||
77
SUPPORTED_MODELS.md
Normal file
77
SUPPORTED_MODELS.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Supported AI Providers and Models in K8sGPT
|
||||
|
||||
K8sGPT supports a variety of AI/LLM providers (backends). Some providers have a fixed set of supported models, while others allow you to specify any model supported by the provider.
|
||||
|
||||
---
|
||||
|
||||
## Providers and Supported Models
|
||||
|
||||
### OpenAI
|
||||
- **Model:** User-configurable (any model supported by OpenAI, e.g., `gpt-3.5-turbo`, `gpt-4`, etc.)
|
||||
|
||||
### Azure OpenAI
|
||||
- **Model:** User-configurable (any model deployed in your Azure OpenAI resource)
|
||||
|
||||
### LocalAI
|
||||
- **Model:** User-configurable (default: `llama3`)
|
||||
|
||||
### Ollama
|
||||
- **Model:** User-configurable (default: `llama3`, others can be specified)
|
||||
|
||||
### NoOpAI
|
||||
- **Model:** N/A (no real model, used for testing)
|
||||
|
||||
### Cohere
|
||||
- **Model:** User-configurable (any model supported by Cohere)
|
||||
|
||||
### Amazon Bedrock
|
||||
- **Supported Models:**
|
||||
- anthropic.claude-sonnet-4-20250514-v1:0
|
||||
- us.anthropic.claude-sonnet-4-20250514-v1:0
|
||||
- eu.anthropic.claude-sonnet-4-20250514-v1:0
|
||||
- apac.anthropic.claude-sonnet-4-20250514-v1:0
|
||||
- us.anthropic.claude-3-7-sonnet-20250219-v1:0
|
||||
- eu.anthropic.claude-3-7-sonnet-20250219-v1:0
|
||||
- apac.anthropic.claude-3-7-sonnet-20250219-v1:0
|
||||
- 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
|
||||
|
||||
### Amazon SageMaker
|
||||
- **Model:** User-configurable (any model deployed in your SageMaker endpoint)
|
||||
|
||||
### Google GenAI
|
||||
- **Model:** User-configurable (any model supported by Google GenAI, e.g., `gemini-pro`)
|
||||
|
||||
### Huggingface
|
||||
- **Model:** User-configurable (any model supported by Huggingface Inference API)
|
||||
|
||||
### Google VertexAI
|
||||
- **Supported Models:**
|
||||
- gemini-1.0-pro-001
|
||||
|
||||
### OCI GenAI
|
||||
- **Model:** User-configurable (any model supported by OCI GenAI)
|
||||
|
||||
### Custom REST
|
||||
- **Model:** User-configurable (any model your custom REST endpoint supports)
|
||||
|
||||
### IBM Watsonx
|
||||
- **Supported Models:**
|
||||
- ibm/granite-13b-chat-v2
|
||||
|
||||
---
|
||||
|
||||
For more details on configuring each provider and model, refer to the official K8sGPT documentation and the provider's own documentation.
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
43
go.mod
43
go.mod
@@ -14,7 +14,7 @@ require (
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/term v0.30.0
|
||||
helm.sh/helm/v3 v3.17.3
|
||||
helm.sh/helm/v3 v3.17.4
|
||||
k8s.io/api v0.32.2
|
||||
k8s.io/apimachinery v0.32.2
|
||||
k8s.io/client-go v0.32.2
|
||||
@@ -36,11 +36,14 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0
|
||||
github.com/IBM/watsonx-go v1.0.1
|
||||
github.com/agiledragon/gomonkey/v2 v2.13.0
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/aws/aws-sdk-go v1.55.7
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0
|
||||
github.com/cohere-ai/cohere-go/v2 v2.12.2
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/google/generative-ai-go v0.19.0
|
||||
github.com/google/martian v2.1.0+incompatible
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1
|
||||
github.com/hupe1980/go-huggingface v0.0.15
|
||||
github.com/kyverno/policy-reporter-kyverno-plugin v1.6.4
|
||||
@@ -51,6 +54,7 @@ require (
|
||||
github.com/pterm/pterm v0.12.80
|
||||
google.golang.org/api v0.218.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
sigs.k8s.io/controller-runtime v0.19.3
|
||||
sigs.k8s.io/gateway-api v1.2.1
|
||||
)
|
||||
@@ -78,12 +82,26 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.4 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect
|
||||
github.com/aws/smithy-go v1.22.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/continuity v0.4.3 // indirect
|
||||
@@ -99,6 +117,13 @@ require (
|
||||
github.com/expr-lang/expr v1.17.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
@@ -112,7 +137,9 @@ require (
|
||||
github.com/invopop/jsonschema v0.12.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.1 // indirect
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
@@ -131,6 +158,8 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
@@ -142,12 +171,12 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // 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-20241026180704-25f6002b00f3 // indirect
|
||||
)
|
||||
|
||||
@@ -271,7 +300,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-20250321185631-1f6e0b77f77e
|
||||
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
|
||||
|
||||
83
go.sum
83
go.sum
@@ -735,12 +735,40 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk=
|
||||
github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
|
||||
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
|
||||
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0 h1:2P70khV5KDzoRs8UuplU3rAzzyLaj5kzND33Jutwpbg=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrock v1.33.0/go.mod h1:rZOgAxQVRg9v5ZEQHrrKw0Gkb9DBAASeeRiwUmmXcG0=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0 h1:eMOwQ8ZZK+76+08RfxeaGUtRFN6wxmD1rvqovc2kq2w=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.30.0/go.mod h1:0b5Rq7rUvSQFYHI1UO0zFTV/S6j6DUyuykXA80C+YOI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
|
||||
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
|
||||
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -761,6 +789,10 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
@@ -778,6 +810,10 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -890,7 +926,13 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
@@ -923,6 +965,14 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
|
||||
github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
@@ -935,6 +985,8 @@ github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
@@ -1180,6 +1232,7 @@ github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -1202,6 +1255,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
@@ -1445,6 +1500,10 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
|
||||
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
@@ -1517,6 +1576,9 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -2198,8 +2260,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg=
|
||||
helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
|
||||
helm.sh/helm/v3 v3.17.4 h1:GK+vgn9gKCyoH44+f3B5zpA78iH3AK4ywIInDEmmn/g=
|
||||
helm.sh/helm/v3 v3.17.4/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -2228,8 +2290,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-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
|
||||
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/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=
|
||||
@@ -2266,6 +2328,7 @@ modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw
|
||||
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=
|
||||
oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
@@ -8,22 +8,22 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/bedrockruntime/bedrockruntimeiface"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/bedrockruntime"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrock"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
|
||||
)
|
||||
|
||||
const amazonbedrockAIClientName = "amazonbedrock"
|
||||
|
||||
// AmazonBedRockClient represents the client for interacting with the AmazonCompletion Bedrock service.
|
||||
// AmazonBedRockClient represents the client for interacting with the Amazon Bedrock service.
|
||||
type AmazonBedRockClient struct {
|
||||
nopCloser
|
||||
|
||||
client bedrockruntimeiface.BedrockRuntimeAPI
|
||||
client BedrockRuntimeAPI
|
||||
mgmtClient BedrockManagementAPI
|
||||
model *bedrock_support.BedrockModel
|
||||
temperature float32
|
||||
topP float32
|
||||
@@ -42,6 +42,8 @@ const (
|
||||
AP_Northeast_1 = "ap-northeast-1"
|
||||
EU_Central_1 = "eu-central-1"
|
||||
AP_South_1 = "ap-south-1"
|
||||
US_Gov_West_1 = "us-gov-west-1"
|
||||
US_Gov_East_1 = "us-gov-east-1"
|
||||
)
|
||||
|
||||
var BEDROCKER_SUPPORTED_REGION = []string{
|
||||
@@ -51,9 +53,96 @@ var BEDROCKER_SUPPORTED_REGION = []string{
|
||||
AP_Northeast_1,
|
||||
EU_Central_1,
|
||||
AP_South_1,
|
||||
US_Gov_West_1,
|
||||
US_Gov_East_1,
|
||||
}
|
||||
|
||||
var defaultModels = []bedrock_support.BedrockModel{
|
||||
|
||||
{
|
||||
Name: "anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
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-sonnet-4-20250514-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "eu.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "eu.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "apac.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "apac.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "eu.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "eu.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "apac.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
// sensible defaults
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "apac.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
@@ -68,8 +157,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,
|
||||
@@ -227,13 +316,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",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -250,7 +340,6 @@ func NewAmazonBedRockClient(models []bedrock_support.BedrockModel) *AmazonBedRoc
|
||||
|
||||
// GetModelOrDefault check config region
|
||||
func GetRegionOrDefault(region string) string {
|
||||
|
||||
if os.Getenv("AWS_DEFAULT_REGION") != "" {
|
||||
region = os.Getenv("AWS_DEFAULT_REGION")
|
||||
}
|
||||
@@ -265,6 +354,17 @@ func GetRegionOrDefault(region string) string {
|
||||
return BEDROCK_DEFAULT_REGION
|
||||
}
|
||||
|
||||
func validateModelArn(model string) bool {
|
||||
var re = regexp.MustCompile(`(?m)^arn:(?P<Partition>[^:\n]*):bedrock:(?P<Region>[^:\n]*):(?P<AccountID>[^:\n]*):(?P<Ignore>(?P<ResourceType>[^:\/\n]*)[:\/])?(?P<Resource>.*)$`)
|
||||
return re.MatchString(model)
|
||||
}
|
||||
|
||||
func validateInferenceProfileArn(inferenceProfile string) bool {
|
||||
// Support both inference-profile and application-inference-profile formats
|
||||
var re = regexp.MustCompile(`(?m)^arn:(?P<Partition>[^:\n]*):bedrock:(?P<Region>[^:\n]*):(?P<AccountID>[^:\n]*):(?:inference-profile|application-inference-profile)\/(?P<ProfileName>.+)$`)
|
||||
return re.MatchString(inferenceProfile)
|
||||
}
|
||||
|
||||
// Get model from string
|
||||
func (a *AmazonBedRockClient) getModelFromString(model string) (*bedrock_support.BedrockModel, error) {
|
||||
if model == "" {
|
||||
@@ -273,7 +373,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 {
|
||||
@@ -284,31 +383,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
|
||||
|
||||
func validateModelArn(model string) bool {
|
||||
var re = regexp.MustCompile(`(?m)^arn:(?P<Partition>[^:\n]*):bedrock:(?P<Region>[^:\n]*):(?P<AccountID>[^:\n]*):(?P<Ignore>(?P<ResourceType>[^:\/\n]*)[:\/])?(?P<Resource>.*)$`)
|
||||
return re.MatchString(model)
|
||||
// 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.
|
||||
@@ -318,26 +413,80 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
a.models = defaultModels
|
||||
}
|
||||
|
||||
// Create a new AWS session
|
||||
providerRegion := GetRegionOrDefault(config.GetProviderRegion())
|
||||
// Get the model input
|
||||
modelInput := config.GetModel()
|
||||
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(providerRegion),
|
||||
})
|
||||
// Determine the appropriate region to use
|
||||
var region string
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
// Check if the model input is actually an inference profile ARN
|
||||
if validateInferenceProfileArn(modelInput) {
|
||||
// Extract the region from the inference profile ARN
|
||||
arnParts := strings.Split(modelInput, ":")
|
||||
if len(arnParts) >= 4 {
|
||||
region = arnParts[3]
|
||||
} else {
|
||||
return fmt.Errorf("could not extract region from inference profile ARN: %s", modelInput)
|
||||
}
|
||||
} else {
|
||||
// Use the provided region or default
|
||||
region = GetRegionOrDefault(config.GetProviderRegion())
|
||||
}
|
||||
|
||||
foundModel, err := a.getModelFromString(config.GetModel())
|
||||
if err != nil {
|
||||
return err
|
||||
// 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(),
|
||||
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)
|
||||
}
|
||||
|
||||
// Create clients with the config
|
||||
a.client = bedrockruntime.NewFromConfig(cfg)
|
||||
a.mgmtClient = bedrock.NewFromConfig(cfg)
|
||||
}
|
||||
|
||||
// Create a new BedrockRuntime client
|
||||
a.client = bedrockruntime.New(sess)
|
||||
a.model = foundModel
|
||||
a.model.Config.ModelName = foundModel.Config.ModelName
|
||||
// Handle model selection based on input type
|
||||
if validateInferenceProfileArn(modelInput) {
|
||||
// Get the inference profile details
|
||||
profile, err := a.getInferenceProfile(context.Background(), modelInput)
|
||||
if err != nil {
|
||||
// Instead of using a fallback model, throw an error
|
||||
return fmt.Errorf("failed to get inference profile: %v", err)
|
||||
} else {
|
||||
// Extract the model ID from the inference profile
|
||||
modelID, err := a.extractModelFromInferenceProfile(profile)
|
||||
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 {
|
||||
// Instead of using a fallback model, throw an 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
|
||||
}
|
||||
} else {
|
||||
// Regular model ID provided
|
||||
foundModel, err := a.getModelFromString(modelInput)
|
||||
if err != nil {
|
||||
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()
|
||||
a.maxTokens = config.GetMaxTokens()
|
||||
@@ -345,18 +494,87 @@ func (a *AmazonBedRockClient) Configure(config IAIConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getInferenceProfile retrieves the inference profile details from Amazon Bedrock
|
||||
func (a *AmazonBedRockClient) getInferenceProfile(ctx context.Context, inferenceProfileARN string) (*bedrock.GetInferenceProfileOutput, error) {
|
||||
// Extract the profile ID from the ARN
|
||||
// ARN format: arn:aws:bedrock:region:account-id:inference-profile/profile-id
|
||||
// or arn:aws:bedrock:region:account-id:application-inference-profile/profile-id
|
||||
parts := strings.Split(inferenceProfileARN, "/")
|
||||
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
|
||||
}
|
||||
|
||||
// extractModelFromInferenceProfile extracts the model ID from the inference profile
|
||||
func (a *AmazonBedRockClient) extractModelFromInferenceProfile(profile *bedrock.GetInferenceProfileOutput) (string, error) {
|
||||
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
|
||||
}
|
||||
|
||||
// GetCompletion sends a request to the model for generating completion based on the provided prompt.
|
||||
func (a *AmazonBedRockClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
|
||||
// override config defaults
|
||||
a.model.Config.MaxTokens = a.maxTokens
|
||||
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,
|
||||
@@ -364,16 +582,18 @@ 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.InvokeModelWithContext(ctx, params)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
return a.model.Response.ParseResponse(resp.Body)
|
||||
|
||||
}
|
||||
|
||||
// GetName returns the name of the AmazonBedRockClient.
|
||||
|
||||
103
pkg/ai/amazonbedrock_mock_test.go
Normal file
103
pkg/ai/amazonbedrock_mock_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrock"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrock/types"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai/bedrock_support"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Mock for Bedrock Management Client
|
||||
type MockBedrockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockBedrockClient) GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error) {
|
||||
args := m.Called(ctx, params)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*bedrock.GetInferenceProfileOutput), args.Error(1)
|
||||
}
|
||||
|
||||
// Mock for Bedrock Runtime Client
|
||||
type MockBedrockRuntimeClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockBedrockRuntimeClient) InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error) {
|
||||
args := m.Called(ctx, params)
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*bedrockruntime.InvokeModelOutput), args.Error(1)
|
||||
}
|
||||
|
||||
// TestBedrockInferenceProfileARNWithMocks tests the inference profile ARN validation with mocks
|
||||
func TestBedrockInferenceProfileARNWithMocks(t *testing.T) {
|
||||
// Create test models
|
||||
testModels := []bedrock_support.BedrockModel{
|
||||
{
|
||||
Name: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
Completion: &bedrock_support.CohereMessagesCompletion{},
|
||||
Response: &bedrock_support.CohereMessagesResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a client with test models
|
||||
client := &AmazonBedRockClient{models: testModels}
|
||||
|
||||
// Create mock clients
|
||||
mockMgmtClient := new(MockBedrockClient)
|
||||
mockRuntimeClient := new(MockBedrockRuntimeClient)
|
||||
|
||||
// Inject mock clients into the AmazonBedRockClient
|
||||
client.mgmtClient = mockMgmtClient
|
||||
client.client = mockRuntimeClient
|
||||
|
||||
// Test with a valid inference profile ARN
|
||||
inferenceProfileARN := "arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile"
|
||||
|
||||
// Setup mock response for GetInferenceProfile
|
||||
mockMgmtClient.On("GetInferenceProfile", mock.Anything, &bedrock.GetInferenceProfileInput{
|
||||
InferenceProfileIdentifier: aws.String("my-profile"),
|
||||
}).Return(&bedrock.GetInferenceProfileOutput{
|
||||
Models: []types.InferenceProfileModel{
|
||||
{
|
||||
ModelArn: aws.String("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"),
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
// Configure the client with the inference profile ARN
|
||||
config := AIProvider{
|
||||
Model: inferenceProfileARN,
|
||||
ProviderRegion: "us-east-1",
|
||||
}
|
||||
|
||||
// Test the Configure method with the inference profile ARN
|
||||
err := client.Configure(&config)
|
||||
|
||||
// Verify that the configuration was successful
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, inferenceProfileARN, client.model.Config.ModelName)
|
||||
|
||||
// Verify that the mock was called
|
||||
mockMgmtClient.AssertExpectations(t)
|
||||
}
|
||||
@@ -31,25 +31,72 @@ var testModels = []bedrock_support.BedrockModel{
|
||||
ModelName: "anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
Completion: &bedrock_support.CohereCompletion{},
|
||||
Response: &bedrock_support.CohereResponse{},
|
||||
Config: bedrock_support.BedrockModelConfig{
|
||||
MaxTokens: 100,
|
||||
Temperature: 0.5,
|
||||
TopP: 0.9,
|
||||
ModelName: "anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func TestBedrockGetCompletionInferenceProfile(t *testing.T) {
|
||||
@@ -96,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",
|
||||
@@ -162,3 +209,54 @@ func TestDefaultModels(t *testing.T) {
|
||||
assert.NoError(t, err, "Should find the model")
|
||||
assert.Equal(t, "anthropic.claude-v2", model.Name, "Should find the correct model")
|
||||
}
|
||||
|
||||
func TestValidateInferenceProfileArn(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
arn string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "valid inference profile ARN",
|
||||
arn: "arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "valid application inference profile ARN",
|
||||
arn: "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "invalid service in ARN",
|
||||
arn: "arn:aws:s3:us-east-1:123456789012:inference-profile/my-profile",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "invalid resource type in ARN",
|
||||
arn: "arn:aws:bedrock:us-east-1:123456789012:model/my-profile",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "malformed ARN",
|
||||
arn: "arn:aws:bedrock:us-east-1:inference-profile/my-profile",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "not an ARN",
|
||||
arn: "not-an-arn",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
arn: "",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := validateInferenceProfileArn(tt.arn)
|
||||
assert.Equal(t, tt.valid, result, "validateInferenceProfileArn() result should match expected")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
18
pkg/ai/bedrock_interfaces.go
Normal file
18
pkg/ai/bedrock_interfaces.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrock"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
|
||||
)
|
||||
|
||||
// BedrockManagementAPI defines the interface for Bedrock management operations
|
||||
type BedrockManagementAPI interface {
|
||||
GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error)
|
||||
}
|
||||
|
||||
// BedrockRuntimeAPI defines the interface for Bedrock runtime operations
|
||||
type BedrockRuntimeAPI interface {
|
||||
InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -61,10 +61,22 @@ var VERTEXAI_SUPPORTED_REGION = []string{
|
||||
}
|
||||
|
||||
const (
|
||||
ModelGeminiProV1 = "gemini-1.0-pro-001"
|
||||
ModelGeminiProV1 = "gemini-1.0-pro-001" // Retired Model
|
||||
ModelGeminiProV2_5 = "gemini-2.5-pro" // Latest Stable Model
|
||||
ModelGeminiFlashV2_5 = "gemini-2.5-flash" // Latest Stable Model
|
||||
ModelGeminiFlashV2 = "gemini-2.0-flash" // Latest Stable Model
|
||||
ModelGeminiFlashLiteV2 = "gemini-2.0-flash-lite" // Latest Stable Model
|
||||
ModelGeminiProV1_5 = "gemini-1.5-pro-002*" // Legacy Stable Model
|
||||
ModelGeminiFlashV1_5 = "gemini-1.5-flash-002*" // Legacy Stable Model
|
||||
)
|
||||
|
||||
var VERTEXAI_MODELS = []string{
|
||||
ModelGeminiProV2_5,
|
||||
ModelGeminiFlashV2_5,
|
||||
ModelGeminiFlashV2,
|
||||
ModelGeminiFlashLiteV2,
|
||||
ModelGeminiProV1_5,
|
||||
ModelGeminiFlashV1_5,
|
||||
ModelGeminiProV1,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,21 +16,32 @@ package ai
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/v65/common"
|
||||
"github.com/oracle/oci-go-sdk/v65/generativeai"
|
||||
"github.com/oracle/oci-go-sdk/v65/generativeaiinference"
|
||||
"strings"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
const ociClientName = "oci"
|
||||
|
||||
type ociModelVendor string
|
||||
|
||||
const (
|
||||
vendorCohere = "cohere"
|
||||
vendorMeta = "meta"
|
||||
)
|
||||
|
||||
type OCIGenAIClient struct {
|
||||
nopCloser
|
||||
|
||||
client *generativeaiinference.GenerativeAiInferenceClient
|
||||
model string
|
||||
model *generativeai.Model
|
||||
modelID string
|
||||
compartmentId string
|
||||
temperature float32
|
||||
topP float32
|
||||
topK int32
|
||||
maxTokens int
|
||||
}
|
||||
|
||||
@@ -40,9 +51,10 @@ func (c *OCIGenAIClient) GetName() string {
|
||||
|
||||
func (c *OCIGenAIClient) Configure(config IAIConfig) error {
|
||||
config.GetEndpointName()
|
||||
c.model = config.GetModel()
|
||||
c.modelID = config.GetModel()
|
||||
c.temperature = config.GetTemperature()
|
||||
c.topP = config.GetTopP()
|
||||
c.topK = config.GetTopK()
|
||||
c.maxTokens = config.GetMaxTokens()
|
||||
c.compartmentId = config.GetCompartmentId()
|
||||
provider := common.DefaultConfigProvider()
|
||||
@@ -51,47 +63,123 @@ func (c *OCIGenAIClient) Configure(config IAIConfig) error {
|
||||
return err
|
||||
}
|
||||
c.client = &client
|
||||
model, err := c.getModel(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
generateTextRequest := c.newGenerateTextRequest(prompt)
|
||||
generateTextResponse, err := c.client.GenerateText(ctx, generateTextRequest)
|
||||
request := c.newChatRequest(prompt)
|
||||
response, err := c.client.Chat(ctx, request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractGeneratedText(generateTextResponse.InferenceResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractGeneratedText(response.ChatResponse)
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) newGenerateTextRequest(prompt string) generativeaiinference.GenerateTextRequest {
|
||||
temperatureF64 := float64(c.temperature)
|
||||
topPF64 := float64(c.topP)
|
||||
return generativeaiinference.GenerateTextRequest{
|
||||
GenerateTextDetails: generativeaiinference.GenerateTextDetails{
|
||||
func (c *OCIGenAIClient) newChatRequest(prompt string) generativeaiinference.ChatRequest {
|
||||
return generativeaiinference.ChatRequest{
|
||||
ChatDetails: generativeaiinference.ChatDetails{
|
||||
CompartmentId: &c.compartmentId,
|
||||
ServingMode: generativeaiinference.OnDemandServingMode{
|
||||
ModelId: &c.model,
|
||||
},
|
||||
InferenceRequest: generativeaiinference.CohereLlmInferenceRequest{
|
||||
Prompt: &prompt,
|
||||
MaxTokens: &c.maxTokens,
|
||||
Temperature: &temperatureF64,
|
||||
TopP: &topPF64,
|
||||
},
|
||||
ServingMode: c.getServingMode(),
|
||||
ChatRequest: c.getChatModelRequest(prompt),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
func (c *OCIGenAIClient) getChatModelRequest(prompt string) generativeaiinference.BaseChatRequest {
|
||||
temperatureF64 := float64(c.temperature)
|
||||
topPF64 := float64(c.topP)
|
||||
topK := int(c.topK)
|
||||
|
||||
switch c.getVendor() {
|
||||
case vendorMeta:
|
||||
messages := []generativeaiinference.Message{
|
||||
generativeaiinference.UserMessage{
|
||||
Content: []generativeaiinference.ChatContent{
|
||||
generativeaiinference.TextContent{
|
||||
Text: &prompt,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
// 0 is invalid for Meta vendor type, instead use -1 to disable topK sampling.
|
||||
if topK == 0 {
|
||||
topK = -1
|
||||
}
|
||||
return generativeaiinference.GenericChatRequest{
|
||||
Messages: messages,
|
||||
TopK: &topK,
|
||||
TopP: &topPF64,
|
||||
Temperature: &temperatureF64,
|
||||
MaxTokens: &c.maxTokens,
|
||||
}
|
||||
default: // Default to cohere
|
||||
return generativeaiinference.CohereChatRequest{
|
||||
Message: &prompt,
|
||||
MaxTokens: &c.maxTokens,
|
||||
Temperature: &temperatureF64,
|
||||
TopK: &topK,
|
||||
TopP: &topPF64,
|
||||
}
|
||||
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
for _, text := range response.GeneratedTexts {
|
||||
if text.Text != nil {
|
||||
sb.WriteString(*text.Text)
|
||||
}
|
||||
|
||||
func extractGeneratedText(llmInferenceResponse generativeaiinference.BaseChatResponse) (string, error) {
|
||||
switch response := llmInferenceResponse.(type) {
|
||||
case generativeaiinference.GenericChatResponse:
|
||||
if len(response.Choices) > 0 && len(response.Choices[0].Message.GetContent()) > 0 {
|
||||
if content, ok := response.Choices[0].Message.GetContent()[0].(generativeaiinference.TextContent); ok {
|
||||
return *content.Text, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no text found in oci response")
|
||||
case generativeaiinference.CohereChatResponse:
|
||||
return *response.Text, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown oci response type: %s", reflect.TypeOf(llmInferenceResponse).Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getServingMode() generativeaiinference.ServingMode {
|
||||
if c.isBaseModel() {
|
||||
return generativeaiinference.OnDemandServingMode{
|
||||
ModelId: &c.modelID,
|
||||
}
|
||||
}
|
||||
return sb.String(), nil
|
||||
return generativeaiinference.DedicatedServingMode{
|
||||
EndpointId: &c.modelID,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getModel(provider common.ConfigurationProvider) (*generativeai.Model, error) {
|
||||
client, err := generativeai.NewGenerativeAiClientWithConfigurationProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := client.GetModel(context.Background(), generativeai.GetModelRequest{
|
||||
ModelId: &c.modelID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Model, nil
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) isBaseModel() bool {
|
||||
return c.model != nil && c.model.Type == generativeai.ModelTypeBase
|
||||
}
|
||||
|
||||
func (c *OCIGenAIClient) getVendor() ociModelVendor {
|
||||
if c.model == nil || c.model.Vendor == nil {
|
||||
return ""
|
||||
}
|
||||
return ociModelVendor(*c.model.Vendor)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ var coreAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"Service": ServiceAnalyzer{},
|
||||
"Ingress": IngressAnalyzer{},
|
||||
"StatefulSet": StatefulSetAnalyzer{},
|
||||
"Job": JobAnalyzer{},
|
||||
"CronJob": CronJobAnalyzer{},
|
||||
"Node": NodeAnalyzer{},
|
||||
"ValidatingWebhookConfiguration": ValidatingWebhookAnalyzer{},
|
||||
@@ -56,6 +57,8 @@ var additionalAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"HTTPRoute": HTTPRouteAnalyzer{},
|
||||
"Storage": StorageAnalyzer{},
|
||||
"Security": SecurityAnalyzer{},
|
||||
"ClusterCatalog": ClusterCatalogAnalyzer{},
|
||||
"ClusterExtension": ClusterExtensionAnalyzer{},
|
||||
}
|
||||
|
||||
func ListFilters() ([]string, []string, []string) {
|
||||
|
||||
161
pkg/analyzer/clustercatalog.go
Normal file
161
pkg/analyzer/clustercatalog.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type ClusterCatalogAnalyzer struct{}
|
||||
|
||||
func (ClusterCatalogAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "ClusterCatalog"
|
||||
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
var clusterCatalogGVR = schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clustercatalogs",
|
||||
}
|
||||
if a.Client == nil {
|
||||
return nil, fmt.Errorf("client is nil in ClusterCatalogAnalyzer")
|
||||
}
|
||||
if a.Client.GetDynamicClient() == nil {
|
||||
return nil, fmt.Errorf("dynamic client is nil in ClusterCatalogAnalyzer")
|
||||
}
|
||||
|
||||
list, err := a.Client.GetDynamicClient().Resource(clusterCatalogGVR).Namespace("").List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, item := range list.Items {
|
||||
var failures []common.Failure
|
||||
catalog, err := ConvertToClusterCatalog(&item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("ClusterCatalog: %s | Source: %s\n", catalog.Name, catalog.Spec.Source.Image.Ref)
|
||||
failures, err = ValidateClusterCatalog(failures, catalog)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[catalog.Name] = common.PreAnalysis{
|
||||
Catalog: *catalog,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, catalog.Name, "").Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, found := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, err
|
||||
}
|
||||
|
||||
func ConvertToClusterCatalog(u *unstructured.Unstructured) (*common.ClusterCatalog, error) {
|
||||
var cc common.ClusterCatalog
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &cc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to ClusterCatalog: %w", err)
|
||||
}
|
||||
return &cc, nil
|
||||
}
|
||||
|
||||
func addCatalogConditionFailure(failures []common.Failure, catalogName string, catalogCondition metav1.Condition) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("OLMv1 ClusterCatalog: %s has condition of type %s, reason %s: %s", catalogName, catalogCondition.Type, catalogCondition.Reason, catalogCondition.Message),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: catalogName,
|
||||
Masked: util.MaskString(catalogName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func addCatalogFailure(failures []common.Failure, catalogName string, err error) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s has error: %s", catalogName, err.Error()),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: catalogName,
|
||||
Masked: util.MaskString(catalogName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func ValidateClusterCatalog(failures []common.Failure, catalog *common.ClusterCatalog) ([]common.Failure, error) {
|
||||
if !isValidImageRef(catalog.Spec.Source.Image.Ref) {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("invalid image ref format in spec.source.image.ref: %s", catalog.Spec.Source.Image.Ref))
|
||||
}
|
||||
|
||||
// Check status.resolvedSource.image.ref ends with @sha256:...
|
||||
if catalog.Status.ResolvedSource != nil {
|
||||
if catalog.Status.ResolvedSource.Image.Ref == "" {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("missing status.resolvedSource.image.ref"))
|
||||
}
|
||||
if !regexp.MustCompile(`@sha256:[a-f0-9]{64}$`).MatchString(catalog.Status.ResolvedSource.Image.Ref) {
|
||||
failures = addCatalogFailure(failures, catalog.Name, fmt.Errorf("status.resolvedSource.image.ref must end with @sha256:<digest>"))
|
||||
}
|
||||
}
|
||||
|
||||
for _, condition := range catalog.Status.Conditions {
|
||||
if condition.Status != "True" && condition.Type == "Serving" {
|
||||
failures = addCatalogConditionFailure(failures, catalog.Name, condition)
|
||||
}
|
||||
if condition.Type == "Progressing" && condition.Reason != "Succeeded" {
|
||||
failures = addCatalogConditionFailure(failures, catalog.Name, condition)
|
||||
}
|
||||
}
|
||||
|
||||
return failures, nil
|
||||
}
|
||||
|
||||
// isValidImageRef does a simple regex check to validate image refs
|
||||
func isValidImageRef(ref string) bool {
|
||||
pattern := `^([a-zA-Z0-9\-\.]+(?::[0-9]+)?/)?([a-z0-9]+(?:[._\-\/][a-z0-9]+)*)(:[\w][\w.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$`
|
||||
return regexp.MustCompile(pattern).MatchString(ref)
|
||||
}
|
||||
182
pkg/analyzer/clustercatalog_test.go
Normal file
182
pkg/analyzer/clustercatalog_test.go
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestClusterCatalogAnalyzer(t *testing.T) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clustercatalogs",
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
|
||||
scheme,
|
||||
map[schema.GroupVersionResource]string{
|
||||
gvr: "ClusterCatalogList",
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid ClusterCatalog",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Serving",
|
||||
"status": "True",
|
||||
"reason": "Available",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid availabilityMode",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "test",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid pollIntervalMinutes",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "registry.redhat.io/redhat/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterCatalog",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid image reference",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"availabilityMode": "Available",
|
||||
"source": map[string]interface{}{
|
||||
"type": "Image",
|
||||
"image": map[string]interface{}{
|
||||
"ref": "quay.io/test/community-operator-index:v4.19",
|
||||
"pollIntervalMinutes": float64(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
ccAnalyzer := ClusterCatalogAnalyzer{}
|
||||
results, err := ccAnalyzer.Analyze(config)
|
||||
for _, res := range results {
|
||||
fmt.Printf("Result: %s | Failures: %d\n", res.Name, len(res.Error))
|
||||
for _, err := range res.Error {
|
||||
fmt.Printf(" - %s\n", err)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(results))
|
||||
}
|
||||
148
pkg/analyzer/clusterextension.go
Normal file
148
pkg/analyzer/clusterextension.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2023 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type ClusterExtensionAnalyzer struct{}
|
||||
|
||||
func (ClusterExtensionAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
kind := "ClusterExtension"
|
||||
|
||||
AnalyzerErrorsMetric.DeletePartialMatch(map[string]string{
|
||||
"analyzer_name": kind,
|
||||
})
|
||||
|
||||
var clusterExtensionGVR = schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clusterextensions",
|
||||
}
|
||||
if a.Client == nil {
|
||||
return nil, fmt.Errorf("client is nil in ClusterExtensionAnalyzer")
|
||||
}
|
||||
if a.Client.GetDynamicClient() == nil {
|
||||
return nil, fmt.Errorf("dynamic client is nil in ClusterExtensionAnalyzer")
|
||||
}
|
||||
|
||||
list, err := a.Client.GetDynamicClient().Resource(clusterExtensionGVR).Namespace("").List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, item := range list.Items {
|
||||
var failures []common.Failure
|
||||
extension, err := ConvertToClusterExtension(&item)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("ClusterExtension: %s | Source: %s\n", extension.Name, extension.Spec.Source.Catalog.PackageName)
|
||||
failures, err = ValidateClusterExtension(failures, extension)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[extension.Name] = common.PreAnalysis{
|
||||
Extension: *extension,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, extension.Name, "").Set(float64(len(failures)))
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, found := util.GetParent(a.Client, value.Node.ObjectMeta)
|
||||
if found {
|
||||
currentAnalysis.ParentObject = parent
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, err
|
||||
}
|
||||
|
||||
func ConvertToClusterExtension(u *unstructured.Unstructured) (*common.ClusterExtension, error) {
|
||||
var ce common.ClusterExtension
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ce)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to ClusterExtension: %w", err)
|
||||
}
|
||||
return &ce, nil
|
||||
}
|
||||
|
||||
func addExtensionConditionFailure(failures []common.Failure, extensionName string, extensionCondition metav1.Condition) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("OLMv1 ClusterExtension: %s has condition of type %s, reason %s: %s", extensionName, extensionCondition.Type, extensionCondition.Reason, extensionCondition.Message),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: extensionName,
|
||||
Masked: util.MaskString(extensionName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func addExtensionFailure(failures []common.Failure, extensionName string, err error) []common.Failure {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s has error: %s", extensionName, err.Error()),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: extensionName,
|
||||
Masked: util.MaskString(extensionName),
|
||||
},
|
||||
},
|
||||
})
|
||||
return failures
|
||||
}
|
||||
|
||||
func ValidateClusterExtension(failures []common.Failure, extension *common.ClusterExtension) ([]common.Failure, error) {
|
||||
if extension.Spec.Source.Catalog != nil && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != "CatalogProvided" && extension.Spec.Source.Catalog.UpgradeConstraintPolicy != "SelfCertified" {
|
||||
failures = addExtensionFailure(failures, extension.Name, fmt.Errorf("invalid or missing extension.Spec.Source.Catalog.UpgradeConstraintPolicy (expecting 'SelfCertified' or 'CatalogProvided')"))
|
||||
}
|
||||
|
||||
if extension.Spec.Source.SourceType != "Catalog" {
|
||||
failures = addExtensionFailure(failures, extension.Name, fmt.Errorf("invalid or missing spec.source.sourceType (expecting 'Catalog')"))
|
||||
}
|
||||
|
||||
for _, condition := range extension.Status.Conditions {
|
||||
if condition.Status != "True" && condition.Type == "Installed" {
|
||||
failures = addExtensionConditionFailure(failures, extension.Name, condition)
|
||||
}
|
||||
if condition.Type == "Progressing" && condition.Reason != "Succeeded" {
|
||||
failures = addExtensionConditionFailure(failures, extension.Name, condition)
|
||||
}
|
||||
}
|
||||
|
||||
return failures, nil
|
||||
}
|
||||
179
pkg/analyzer/clusterextension_test.go
Normal file
179
pkg/analyzer/clusterextension_test.go
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestClusterExtensionAnalyzer(t *testing.T) {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "olm.operatorframework.io",
|
||||
Version: "v1",
|
||||
Resource: "clusterextensions",
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
|
||||
scheme,
|
||||
map[schema.GroupVersionResource]string{
|
||||
gvr: "ClusterExtensionList",
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid SelfCertified ClusterExtension",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "SelfCertified",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Valid CatalogProvided ClusterExtension",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "CatalogProvided",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "True",
|
||||
"reason": "Succeeded",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid UpgradeConstraintPolicy",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Catalog",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "InvalidPolicy",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "False",
|
||||
"reason": "Failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "olm.operatorframework.io/v1",
|
||||
"kind": "ClusterExtension",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "Invalid SourceType",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"source": map[string]interface{}{
|
||||
"sourceType": "Git",
|
||||
"catalog": map[string]interface{}{
|
||||
"upgradeConstraintPolicy": "CatalogProvided",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "Progressing",
|
||||
"status": "True",
|
||||
"reason": "Retrying",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"type": "Installed",
|
||||
"status": "False",
|
||||
"reason": "Failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(),
|
||||
DynamicClient: dynamicClient,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
ceAnalyzer := ClusterExtensionAnalyzer{}
|
||||
results, err := ceAnalyzer.Analyze(config)
|
||||
for _, res := range results {
|
||||
fmt.Printf("Result: %s | Failures: %d\n", res.Name, len(res.Error))
|
||||
for _, err := range res.Error {
|
||||
fmt.Printf(" - %s\n", err)
|
||||
}
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(results))
|
||||
}
|
||||
@@ -54,22 +54,41 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
|
||||
for _, deployment := range deployments.Items {
|
||||
var failures []common.Failure
|
||||
if *deployment.Spec.Replicas != deployment.Status.Replicas {
|
||||
doc := apiDoc.GetApiDocV2("spec.replicas")
|
||||
if *deployment.Spec.Replicas != deployment.Status.ReadyReplicas {
|
||||
if deployment.Status.Replicas > *deployment.Spec.Replicas {
|
||||
doc := apiDoc.GetApiDocV2("spec.replicas")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Deployment %s/%s has %d replicas but %d are available", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.Replicas),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: deployment.Namespace,
|
||||
Masked: util.MaskString(deployment.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: deployment.Name,
|
||||
Masked: util.MaskString(deployment.Name),
|
||||
},
|
||||
}})
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Deployment %s/%s has %d replicas in spec but %d replicas in status because status field is not updated yet after scaling and %d replicas are available with status running", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.Replicas, deployment.Status.ReadyReplicas),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: deployment.Namespace,
|
||||
Masked: util.MaskString(deployment.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: deployment.Name,
|
||||
Masked: util.MaskString(deployment.Name),
|
||||
},
|
||||
}})
|
||||
|
||||
} else {
|
||||
doc := apiDoc.GetApiDocV2("spec.replicas")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Deployment %s/%s has %d replicas but %d are available with status running", deployment.Namespace, deployment.Name, *deployment.Spec.Replicas, deployment.Status.ReadyReplicas),
|
||||
KubernetesDoc: doc,
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: deployment.Namespace,
|
||||
Masked: util.MaskString(deployment.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: deployment.Name,
|
||||
Masked: util.MaskString(deployment.Name),
|
||||
},
|
||||
}})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name)] = common.PreAnalysis{
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
|
||||
)
|
||||
|
||||
@@ -68,6 +69,8 @@ type PreAnalysis struct {
|
||||
ScaledObject keda.ScaledObject
|
||||
KyvernoPolicyReport kyverno.PolicyReport
|
||||
KyvernoClusterPolicyReport kyverno.ClusterPolicyReport
|
||||
Catalog ClusterCatalog
|
||||
Extension ClusterExtension
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
@@ -93,3 +96,117 @@ type Sensitive struct {
|
||||
Unmasked string
|
||||
Masked string
|
||||
}
|
||||
|
||||
type (
|
||||
SourceType string
|
||||
AvailabilityMode string
|
||||
UpgradeConstraintPolicy string
|
||||
CRDUpgradeSafetyEnforcement string
|
||||
)
|
||||
|
||||
type ClusterCatalog struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
Spec ClusterCatalogSpec `json:"spec"`
|
||||
Status ClusterCatalogStatus `json:"status,omitempty"`
|
||||
}
|
||||
type ClusterCatalogList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []ClusterCatalog `json:"items"`
|
||||
}
|
||||
|
||||
type ClusterCatalogSpec struct {
|
||||
Source CatalogSource `json:"source"`
|
||||
|
||||
Priority int32 `json:"priority"`
|
||||
|
||||
AvailabilityMode AvailabilityMode `json:"availabilityMode,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterCatalogStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
||||
|
||||
ResolvedSource *ResolvedCatalogSource `json:"resolvedSource,omitempty"`
|
||||
|
||||
URLs *ClusterCatalogURLs `json:"urls,omitempty"`
|
||||
LastUnpacked *metav1.Time `json:"lastUnpacked,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterCatalogURLs struct {
|
||||
Base string `json:"base"`
|
||||
}
|
||||
type CatalogSource struct {
|
||||
Type SourceType `json:"type"`
|
||||
Image *ImageSource `json:"image,omitempty"`
|
||||
}
|
||||
type ResolvedCatalogSource struct {
|
||||
Type SourceType `json:"type"`
|
||||
Image *ResolvedImageSource `json:"image"`
|
||||
}
|
||||
type ResolvedImageSource struct {
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
|
||||
type ImageSource struct {
|
||||
Ref string `json:"ref"`
|
||||
PollIntervalMinutes *int `json:"pollIntervalMinutes,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtension struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec ClusterExtensionSpec `json:"spec,omitempty"`
|
||||
Status ClusterExtensionStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionSpec struct {
|
||||
Namespace string `json:"namespace"`
|
||||
ServiceAccount ServiceAccountReference `json:"serviceAccount"`
|
||||
Source SourceConfig `json:"source"`
|
||||
Install *ClusterExtensionInstallConfig `json:"install,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionInstallConfig struct {
|
||||
Preflight *PreflightConfig `json:"preflight,omitempty"`
|
||||
}
|
||||
|
||||
type PreflightConfig struct {
|
||||
CRDUpgradeSafety *CRDUpgradeSafetyPreflightConfig `json:"crdUpgradeSafety"`
|
||||
}
|
||||
|
||||
type CRDUpgradeSafetyPreflightConfig struct {
|
||||
Enforcement CRDUpgradeSafetyEnforcement `json:"enforcement"`
|
||||
}
|
||||
|
||||
type ServiceAccountReference struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type SourceConfig struct {
|
||||
SourceType string `json:"sourceType"`
|
||||
Catalog *CatalogFilter `json:"catalog,omitempty"`
|
||||
}
|
||||
|
||||
type CatalogFilter struct {
|
||||
PackageName string `json:"packageName"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
UpgradeConstraintPolicy UpgradeConstraintPolicy `json:"upgradeConstraintPolicy,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
|
||||
|
||||
Install *ClusterExtensionInstallStatus `json:"install,omitempty"`
|
||||
}
|
||||
|
||||
type ClusterExtensionInstallStatus struct {
|
||||
Bundle BundleMetadata `json:"bundle"`
|
||||
}
|
||||
|
||||
type BundleMetadata struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ limitations under the License.
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -33,6 +34,10 @@ func (c *Client) GetCtrlClient() ctrl.Client {
|
||||
return c.CtrlClient
|
||||
}
|
||||
|
||||
func (c *Client) GetDynamicClient() dynamic.Interface {
|
||||
return c.DynamicClient
|
||||
}
|
||||
|
||||
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
var config *rest.Config
|
||||
config, err := rest.InClusterConfig()
|
||||
@@ -69,10 +74,16 @@ func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
Client: clientSet,
|
||||
CtrlClient: ctrlClient,
|
||||
Config: config,
|
||||
ServerVersion: serverVersion,
|
||||
DynamicClient: dynamicClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
openapi_v2 "github.com/google/gnostic/openapiv2"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -14,6 +15,7 @@ type Client struct {
|
||||
CtrlClient ctrl.Client
|
||||
Config *rest.Config
|
||||
ServerVersion *version.Info
|
||||
DynamicClient dynamic.Interface
|
||||
}
|
||||
|
||||
type K8sApiReference struct {
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/server/config"
|
||||
mcp_golang "github.com/metoro-io/mcp-golang"
|
||||
mcp_http "github.com/metoro-io/mcp-golang/transport/http"
|
||||
"github.com/metoro-io/mcp-golang/transport/stdio"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
@@ -41,10 +41,19 @@ type MCPServer struct {
|
||||
|
||||
// NewMCPServer creates a new MCP server
|
||||
func NewMCPServer(port string, aiProvider *ai.AIProvider, useHTTP bool, logger *zap.Logger) (*MCPServer, error) {
|
||||
// Create MCP server with stdio transport
|
||||
transport := stdio.NewStdioServerTransport()
|
||||
opts := []mcp_golang.ServerOptions{
|
||||
mcp_golang.WithName("k8sgpt"),
|
||||
mcp_golang.WithVersion("1.0.0"),
|
||||
}
|
||||
|
||||
server := mcp_golang.NewServer(transport)
|
||||
var server *mcp_golang.Server
|
||||
if useHTTP {
|
||||
logger.Info("starting MCP server with http transport on port", zap.String("port", port))
|
||||
httpTransport := mcp_http.NewHTTPTransport("/mcp").WithAddr(":" + port)
|
||||
server = mcp_golang.NewServer(httpTransport, opts...)
|
||||
} else {
|
||||
server = mcp_golang.NewServer(stdio.NewStdioServerTransport(), opts...)
|
||||
}
|
||||
|
||||
return &MCPServer{
|
||||
server: server,
|
||||
@@ -86,20 +95,11 @@ func (s *MCPServer) Start() error {
|
||||
return fmt.Errorf("failed to register prompts: %v", err)
|
||||
}
|
||||
|
||||
if s.useHTTP {
|
||||
// Start HTTP server
|
||||
go func() {
|
||||
http.HandleFunc("/mcp/analyze", s.handleAnalyzeHTTP)
|
||||
http.HandleFunc("/mcp", s.handleSSE)
|
||||
s.logger.Info("Starting MCP server on port", zap.String("port", s.port))
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%s", s.port), nil); err != nil {
|
||||
s.logger.Error("Error starting HTTP server", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
// Start the server (this will block)
|
||||
if err := s.server.Serve(); err != nil {
|
||||
s.logger.Error("Error starting MCP server", zap.Error(err))
|
||||
}
|
||||
|
||||
// Start the server
|
||||
return s.server.Serve()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AnalyzeRequest represents the input parameters for the analyze tool
|
||||
@@ -327,7 +327,7 @@ func (s *MCPServer) registerResources() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MCPServer) getClusterInfo(ctx context.Context) (interface{}, error) {
|
||||
func (s *MCPServer) getClusterInfo(ctx context.Context) (*mcp_golang.ResourceResponse, error) {
|
||||
// Create a new Kubernetes client
|
||||
client, err := kubernetes.NewClient("", "")
|
||||
if err != nil {
|
||||
@@ -340,74 +340,27 @@ func (s *MCPServer) getClusterInfo(ctx context.Context) (interface{}, error) {
|
||||
return nil, fmt.Errorf("failed to get cluster version: %v", err)
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
data, err := json.Marshal(map[string]string{
|
||||
"version": version.String(),
|
||||
"platform": version.Platform,
|
||||
"gitVersion": version.GitVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleSSE handles Server-Sent Events for MCP
|
||||
func (s *MCPServer) handleSSE(w http.ResponseWriter, r *http.Request) {
|
||||
// Set headers for SSE
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
// Create a channel to receive messages
|
||||
msgChan := make(chan string)
|
||||
defer close(msgChan)
|
||||
|
||||
// Start a goroutine to handle the stdio transport
|
||||
go func() {
|
||||
// TODO: Implement message handling between HTTP and stdio transport
|
||||
// This would require implementing a custom transport that bridges HTTP and stdio
|
||||
|
||||
}()
|
||||
|
||||
// Send messages to the client
|
||||
for msg := range msgChan {
|
||||
if _, err := fmt.Fprintf(w, "data: %s\n\n", msg); err != nil {
|
||||
s.logger.Error("Failed to write SSE message", zap.Error(err))
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// handleAnalyzeHTTP handles HTTP requests for the analyze endpoint
|
||||
func (s *MCPServer) handleAnalyzeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the request body
|
||||
var req AnalyzeRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Failed to decode request: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate MaxConcurrency to prevent excessive memory allocation
|
||||
req.MaxConcurrency = validateMaxConcurrency(req.MaxConcurrency)
|
||||
|
||||
// Call the analyze handler
|
||||
resp, err := s.handleAnalyze(r.Context(), &req)
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Failed to analyze: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set response headers
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// Write the response
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
s.logger.Error("Failed to encode response", zap.Error(err))
|
||||
return mcp_golang.NewResourceResponse(
|
||||
mcp_golang.NewTextEmbeddedResource(
|
||||
"cluster-info",
|
||||
"Failed to marshal cluster info",
|
||||
"text/plain",
|
||||
),
|
||||
), nil
|
||||
}
|
||||
return mcp_golang.NewResourceResponse(
|
||||
mcp_golang.NewTextEmbeddedResource(
|
||||
"cluster-info",
|
||||
string(data),
|
||||
"application/json",
|
||||
),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Close closes the MCP server and releases resources
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
@@ -14,7 +17,12 @@ import (
|
||||
|
||||
func TestServe(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer logger.Sync()
|
||||
defer func() {
|
||||
err := logger.Sync()
|
||||
if err != nil {
|
||||
t.Logf("logger.Sync() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
s := &Config{
|
||||
Port: "50059",
|
||||
@@ -34,7 +42,11 @@ func TestServe(t *testing.T) {
|
||||
|
||||
conn, err := grpc.Dial("localhost:50059", grpc.WithInsecure())
|
||||
assert.NoError(t, err, "Should be able to dial the server")
|
||||
defer conn.Close()
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Logf("failed to close connection: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Test a simple gRPC reflection request
|
||||
cli := grpc_reflection_v1alpha.NewServerReflectionClient(conn)
|
||||
@@ -49,12 +61,172 @@ func TestServe(t *testing.T) {
|
||||
assert.NoError(t, err, "Shutdown should not return an error")
|
||||
}
|
||||
|
||||
// TestMCPServerCreation tests the creation of an MCP server
|
||||
func TestMCPServerCreation(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer func() {
|
||||
err := logger.Sync()
|
||||
if err != nil {
|
||||
t.Logf("logger.Sync() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
aiProvider := &ai.AIProvider{
|
||||
Name: "test-provider",
|
||||
Password: "test-password",
|
||||
Model: "test-model",
|
||||
}
|
||||
|
||||
// Test HTTP mode
|
||||
mcpServer, err := NewMCPServer("8089", aiProvider, true, logger)
|
||||
assert.NoError(t, err, "Should be able to create MCP server with HTTP transport")
|
||||
assert.NotNil(t, mcpServer, "MCP server should not be nil")
|
||||
assert.True(t, mcpServer.useHTTP, "MCP server should be in HTTP mode")
|
||||
assert.Equal(t, "8089", mcpServer.port, "Port should be set correctly")
|
||||
|
||||
// Test stdio mode
|
||||
mcpServerStdio, err := NewMCPServer("8089", aiProvider, false, logger)
|
||||
assert.NoError(t, err, "Should be able to create MCP server with stdio transport")
|
||||
assert.NotNil(t, mcpServerStdio, "MCP server should not be nil")
|
||||
assert.False(t, mcpServerStdio.useHTTP, "MCP server should be in stdio mode")
|
||||
}
|
||||
|
||||
// TestMCPServerBasicHTTP tests basic HTTP connectivity to the MCP server
|
||||
func TestMCPServerBasicHTTP(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer func() {
|
||||
err := logger.Sync()
|
||||
if err != nil {
|
||||
t.Logf("logger.Sync() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
aiProvider := &ai.AIProvider{
|
||||
Name: "test-provider",
|
||||
Password: "test-password",
|
||||
Model: "test-model",
|
||||
}
|
||||
|
||||
mcpServer, err := NewMCPServer("8089", aiProvider, true, logger)
|
||||
assert.NoError(t, err, "Should be able to create MCP server")
|
||||
|
||||
// Start the MCP server in a goroutine
|
||||
go func() {
|
||||
err := mcpServer.Start()
|
||||
// Note: Start() might return an error when the server is stopped, which is expected
|
||||
if err != nil {
|
||||
logger.Info("MCP server stopped", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the server to start
|
||||
err = waitForPort("localhost:8089", 10*time.Second)
|
||||
if err != nil {
|
||||
t.Skipf("MCP server did not start within timeout: %v", err)
|
||||
}
|
||||
|
||||
// Test basic connectivity to the MCP endpoint
|
||||
// The MCP HTTP transport uses a single POST endpoint for all requests
|
||||
resp, err := http.Post("http://localhost:8089/mcp", "application/json", bytes.NewBufferString(`{"jsonrpc":"2.0","id":1,"method":"tools/list"}`))
|
||||
if err != nil {
|
||||
t.Logf("MCP endpoint test skipped (server might not be fully ready): %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Logf("resp.Body.Close() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Accept both 200 and 404 as valid responses (404 means endpoint not implemented)
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("MCP endpoint returned unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
err = mcpServer.Close()
|
||||
assert.NoError(t, err, "MCP server should close without error")
|
||||
}
|
||||
|
||||
// TestMCPServerToolCall tests calling a specific tool (analyze) through the MCP server
|
||||
func TestMCPServerToolCall(t *testing.T) {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer func() {
|
||||
err := logger.Sync()
|
||||
if err != nil {
|
||||
t.Logf("logger.Sync() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
aiProvider := &ai.AIProvider{
|
||||
Name: "test-provider",
|
||||
Password: "test-password",
|
||||
Model: "test-model",
|
||||
}
|
||||
|
||||
mcpServer, err := NewMCPServer("8090", aiProvider, true, logger)
|
||||
assert.NoError(t, err, "Should be able to create MCP server")
|
||||
|
||||
// Start the MCP server in a goroutine
|
||||
go func() {
|
||||
err := mcpServer.Start()
|
||||
if err != nil {
|
||||
logger.Info("MCP server stopped", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for the server to start
|
||||
err = waitForPort("localhost:8090", 10*time.Second)
|
||||
if err != nil {
|
||||
t.Skipf("MCP server did not start within timeout: %v", err)
|
||||
}
|
||||
|
||||
// Test calling the analyze tool with proper JSON-RPC format
|
||||
analyzeRequest := `{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "analyze",
|
||||
"arguments": {
|
||||
"namespace": "default",
|
||||
"backend": "openai",
|
||||
"language": "english",
|
||||
"explain": true,
|
||||
"maxConcurrency": 10
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
resp, err := http.Post("http://localhost:8090/mcp", "application/json", bytes.NewBufferString(analyzeRequest))
|
||||
if err != nil {
|
||||
t.Logf("Analyze tool call test skipped (server might not be fully ready): %v", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Logf("resp.Body.Close() error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Accept both 200 and 404 as valid responses
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("Analyze tool call returned unexpected status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
err = mcpServer.Close()
|
||||
assert.NoError(t, err, "MCP server should close without error")
|
||||
}
|
||||
|
||||
func waitForPort(address string, timeout time.Duration) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
return nil
|
||||
}
|
||||
if time.Since(start) > timeout {
|
||||
|
||||
Reference in New Issue
Block a user