mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 03:23:47 +00:00
Compare commits
293 Commits
chore/read
...
v0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d87b27a3e | ||
|
|
668f8a63fa | ||
|
|
fb7543418b | ||
|
|
c0afc0f5c9 | ||
|
|
cfce828fd1 | ||
|
|
096321b31a | ||
|
|
ca79ef9986 | ||
|
|
0328110f11 | ||
|
|
8b5586901c | ||
|
|
0cf5eab988 | ||
|
|
825e9a43bd | ||
|
|
7a97034cf4 | ||
|
|
5e5d4b6de1 | ||
|
|
34e3e3912e | ||
|
|
8e48f6c6cf | ||
|
|
ced0de6448 | ||
|
|
fabe01aa01 | ||
|
|
258c69a17c | ||
|
|
4d20f70fb4 | ||
|
|
1b7f4ce44a | ||
|
|
3682f5c7eb | ||
|
|
c809af3f47 | ||
|
|
55dda432ab | ||
|
|
428c348586 | ||
|
|
db1388fd20 | ||
|
|
fe261b375f | ||
|
|
b45ff1aa8e | ||
|
|
4984840de1 | ||
|
|
80ac51c804 | ||
|
|
b0e517006e | ||
|
|
61d6e52465 | ||
|
|
a62271661d | ||
|
|
45c7ecf98b | ||
|
|
370e13b1a5 | ||
|
|
a1d8012a5c | ||
|
|
dd5824f436 | ||
|
|
08f2a89e54 | ||
|
|
6f0865413f | ||
|
|
b687473e61 | ||
|
|
11326c1c5f | ||
|
|
fe502e1135 | ||
|
|
21dc61c04f | ||
|
|
705d2a0dce | ||
|
|
fd936ceaf7 | ||
|
|
8a60b57940 | ||
|
|
30e33495e0 | ||
|
|
d2a84ea2b5 | ||
|
|
3845d4747f | ||
|
|
992b107c2d | ||
|
|
63dccdbe6f | ||
|
|
ab55f157ef | ||
|
|
ba01bd4b6e | ||
|
|
a582d444c5 | ||
|
|
9423b53c1d | ||
|
|
5f3a5a54a0 | ||
|
|
83d8571206 | ||
|
|
c8ba7d62d2 | ||
|
|
286983105d | ||
|
|
e449cb6023 | ||
|
|
1069f0c610 | ||
|
|
c041ce2bbb | ||
|
|
5b098fd0d8 | ||
|
|
580ba52dce | ||
|
|
ddef9fa987 | ||
|
|
93bcc627ba | ||
|
|
b6090ed2c0 | ||
|
|
cff69a3926 | ||
|
|
bfa7f32474 | ||
|
|
f5e3ca0bca | ||
|
|
a96d74371f | ||
|
|
7429b933c0 | ||
|
|
337c4a616b | ||
|
|
ba4d701681 | ||
|
|
1f0e9f2445 | ||
|
|
2f21002899 | ||
|
|
22e31661bf | ||
|
|
db40734a0d | ||
|
|
9b243cdcaa | ||
|
|
a24d1f1b30 | ||
|
|
8958a45b9b | ||
|
|
0195bfab30 | ||
|
|
d105a20677 | ||
|
|
eeac731858 | ||
|
|
1abfe8dbca | ||
|
|
df4ee2ad1a | ||
|
|
8ab3573e13 | ||
|
|
e9994b8d16 | ||
|
|
5a59abb55d | ||
|
|
f65f0f5a5f | ||
|
|
1fff4035ea | ||
|
|
ba1352093b | ||
|
|
b46224f297 | ||
|
|
5d5e082f41 | ||
|
|
184920988f | ||
|
|
53c1330538 | ||
|
|
ddc120e7c2 | ||
|
|
e27e9409dc | ||
|
|
fc47c58ae1 | ||
|
|
0f46ceb445 | ||
|
|
8e4ce6a974 | ||
|
|
9ff3fbc382 | ||
|
|
4c773175cd | ||
|
|
67753be6f3 | ||
|
|
075a940d2c | ||
|
|
f8291aab08 | ||
|
|
622bf5824b | ||
|
|
5f30a4ddf4 | ||
|
|
d5bc91fa8a | ||
|
|
44cc8f7ad6 | ||
|
|
35b838bfaf | ||
|
|
b6436378e1 | ||
|
|
508b5c37e1 | ||
|
|
124e46e4ed | ||
|
|
426f562be8 | ||
|
|
91fb06530a | ||
|
|
bbbbd851df | ||
|
|
dde4e833b0 | ||
|
|
5986f4f840 | ||
|
|
213ecd8e83 | ||
|
|
6ead5b356d | ||
|
|
c733292b92 | ||
|
|
3c95a5db82 | ||
|
|
ea1ca44dba | ||
|
|
33319b8ef5 | ||
|
|
1190fe60fd | ||
|
|
ceff0084df | ||
|
|
f6974d0758 | ||
|
|
532a5ce033 | ||
|
|
071ee560f3 | ||
|
|
e1d89920b0 | ||
|
|
96d0d754ea | ||
|
|
86e9564aaf | ||
|
|
9cef52fb8e | ||
|
|
9915f9406b | ||
|
|
a24a8da246 | ||
|
|
f337f6b447 | ||
|
|
80f29dae4f | ||
|
|
a76d60a652 | ||
|
|
dea3ad9ebf | ||
|
|
207f26432c | ||
|
|
3e836d81b7 | ||
|
|
006751567f | ||
|
|
9d9c26214f | ||
|
|
d23da9ae83 | ||
|
|
5dad75fbe9 | ||
|
|
4916fef9d6 | ||
|
|
360387249f | ||
|
|
0e3ef8dd27 | ||
|
|
825ad538fe | ||
|
|
089059675b | ||
|
|
869ba90907 | ||
|
|
391c3fa7a8 | ||
|
|
8e99076497 | ||
|
|
c2b8732a5c | ||
|
|
3ed545f33f | ||
|
|
975813d328 | ||
|
|
9aa0e8960e | ||
|
|
0a124484a2 | ||
|
|
30faf84254 | ||
|
|
c0aebb21a0 | ||
|
|
9ebb7de3c0 | ||
|
|
9e9cd7083d | ||
|
|
a8bf45134f | ||
|
|
4d5449f97a | ||
|
|
25f8dc390c | ||
|
|
32ddf6691c | ||
|
|
6e17c9e285 | ||
|
|
9f0cb6240e | ||
|
|
26db0f6941 | ||
|
|
2acab541bc | ||
|
|
53a19bb04d | ||
|
|
c8f3c946b0 | ||
|
|
b061566404 | ||
|
|
be061da5b6 | ||
|
|
5f548421c7 | ||
|
|
0afd52844b | ||
|
|
f95c1e1d8c | ||
|
|
3fc0d45823 | ||
|
|
cf633b606d | ||
|
|
ab63bd0dd1 | ||
|
|
3e05eb2af2 | ||
|
|
548039ebe6 | ||
|
|
0d5b8ad027 | ||
|
|
0f910e497a | ||
|
|
172c2df6c5 | ||
|
|
2eab0c544f | ||
|
|
f4765bed1b | ||
|
|
14ba8d5550 | ||
|
|
bf49a51c62 | ||
|
|
86c7e81e18 | ||
|
|
cdc7bb1272 | ||
|
|
fe683b71b8 | ||
|
|
cad2b38d03 | ||
|
|
01b2826475 | ||
|
|
4f24368758 | ||
|
|
27b8916f29 | ||
|
|
fe73633273 | ||
|
|
a9d7bd42c2 | ||
|
|
2d481b8563 | ||
|
|
b78ab3d9b5 | ||
|
|
c8b92aaa0e | ||
|
|
65a568e937 | ||
|
|
cb4932c39d | ||
|
|
323a8b23ec | ||
|
|
be5fb62bb7 | ||
|
|
e912a8ded0 | ||
|
|
a46416dce0 | ||
|
|
4d5566b4df | ||
|
|
dbd305f901 | ||
|
|
11c227b82e | ||
|
|
05abe975dd | ||
|
|
3bfb278f81 | ||
|
|
b4865ff233 | ||
|
|
95dfc62c4d | ||
|
|
b3efdf8525 | ||
|
|
5890e3a79c | ||
|
|
d0e4684e82 | ||
|
|
6f90386fc9 | ||
|
|
2f699a9929 | ||
|
|
93b947f261 | ||
|
|
54143ea0ee | ||
|
|
2cb1c9c150 | ||
|
|
02f5f921e2 | ||
|
|
e0141d1cf5 | ||
|
|
d48ff1a942 | ||
|
|
14f076a095 | ||
|
|
829ff566c0 | ||
|
|
0ecf248c3f | ||
|
|
c5c6162df1 | ||
|
|
a270f7c89f | ||
|
|
ff0b67b5a9 | ||
|
|
f9c7daf3dc | ||
|
|
a49cec98c0 | ||
|
|
39c0444fac | ||
|
|
0a0c88719e | ||
|
|
202e8e2977 | ||
|
|
254dbcc4e3 | ||
|
|
9983205ed6 | ||
|
|
57cb563038 | ||
|
|
f602fb59fc | ||
|
|
4bbe583a97 | ||
|
|
1c653ecc51 | ||
|
|
0c231d635e | ||
|
|
931f072e0a | ||
|
|
c3008c5e75 | ||
|
|
bb2db5ca79 | ||
|
|
9c5523f045 | ||
|
|
e113bf7a21 | ||
|
|
009f47c8e8 | ||
|
|
f01f87759a | ||
|
|
06fb8073dc | ||
|
|
7227e441f0 | ||
|
|
a194d4a509 | ||
|
|
0852c658de | ||
|
|
1bc839c48b | ||
|
|
aa77a309e0 | ||
|
|
dc2bfa918c | ||
|
|
abe6394150 | ||
|
|
726d5d45de | ||
|
|
73369240b4 | ||
|
|
d554bba384 | ||
|
|
653b04fcb5 | ||
|
|
aa8203261f | ||
|
|
c1d2863d7c | ||
|
|
88d49ae21c | ||
|
|
deebec629e | ||
|
|
14e85b08ff | ||
|
|
a1093dcfe4 | ||
|
|
961fb6c555 | ||
|
|
9c7d55955b | ||
|
|
b29c6e4582 | ||
|
|
36217667ce | ||
|
|
979f13f043 | ||
|
|
90a30f38bd | ||
|
|
0181c0aeb5 | ||
|
|
51aa59aea8 | ||
|
|
90b3c0898c | ||
|
|
ad594c7cb2 | ||
|
|
cc562a0380 | ||
|
|
80a99757f9 | ||
|
|
c7e8f00458 | ||
|
|
8171ddd15c | ||
|
|
08ba7e1566 | ||
|
|
be67fd03b5 | ||
|
|
9fecc1ea6d | ||
|
|
00e8ec0c88 | ||
|
|
8da8945d1b | ||
|
|
9a093fa1fb | ||
|
|
c8385531e6 | ||
|
|
1a486d4532 | ||
|
|
bdb2e739d4 | ||
|
|
ca8ce8188a | ||
|
|
249c95490b |
12
.github/CODEOWNERS
vendored
Normal file
12
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# CODEOWNERS file indicates code owners for certain files
|
||||
#
|
||||
# Code owners will automatically be added as a reviewer for PRs that touch
|
||||
# the owned files.
|
||||
#
|
||||
|
||||
# Default owners for everything in the repo
|
||||
#
|
||||
# Unless a later match takes precedence, these owners will be requested for
|
||||
# review when someone opens a pull request.
|
||||
|
||||
* @k8sgpt-ai/maintainers @k8sgpt-ai/k8sgpt-approvers
|
||||
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for this project
|
||||
title: 'feature: '
|
||||
labels: 'type:question'
|
||||
assignees: '@k8sgpt-ai/maintainers'
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for initiating this feature request 🤗
|
||||
|
||||
To ensure conciseness, kindly try to adhere to the following format.
|
||||
-->
|
||||
|
||||
Checklist:
|
||||
* [ ] I've searched for similar issues and couldn't find anything matching
|
||||
* [ ] I've discussed this feature in the #k8sgpt slack channel
|
||||
|
||||
## Is this feature request related to a problem?
|
||||
* [ ] Yes
|
||||
* [ ] No
|
||||
|
||||
<!-- If yes, please provide a clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
## Describe the solution you'd like
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## Benefits for the project and its users
|
||||
<!-- Describe the benefits this feature will bring to the project and its users. -->
|
||||
|
||||
## Potential drawbacks
|
||||
<!-- Describe any potential drawbacks this feature might bring to the project and its users. -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context about your feature request here. If applicable, add drawings to help explain. -->
|
||||
50
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
50
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: Question / Bug Report
|
||||
about: Create a report to help us improve
|
||||
title: 'question: '
|
||||
labels: 'type:question'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for initiating this issue request 🤗
|
||||
|
||||
To ensure conciseness, kindly try to adhere to the following format.
|
||||
-->
|
||||
|
||||
Checklist:
|
||||
|
||||
* [ ] I've searched for similar issues and couldn't find anything matching
|
||||
* [ ] I've included steps to reproduce the bug.
|
||||
* [ ] I've included the version of Kubernetes and k8sgpt.
|
||||
|
||||
### Subject of the issue
|
||||
|
||||
<!-- Describe your issue here. -->
|
||||
|
||||
### Your environment
|
||||
|
||||
<!-- Describe your environment here. -->
|
||||
|
||||
* Version of Kubernetes
|
||||
<!-- kubectl version -->
|
||||
* Host OS and its version / If windows, is it WSL?
|
||||
* Version of k8sgpt
|
||||
<!-- k8sgpt version -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!-- Tell us how to reproduce this issue. -->
|
||||
|
||||
* Step 1
|
||||
* Step 2
|
||||
|
||||
### Expected behaviour
|
||||
<!-- Tell us what should happen -->
|
||||
|
||||
### Actual behaviour
|
||||
|
||||
<!-- Tell us what happens instead -->
|
||||
|
||||
### Additional context / screenshots
|
||||
<!-- Add any other context about the problem here. If applicable, add screenshots to help explain. -->
|
||||
12
.github/workflows/release.yaml
vendored
12
.github/workflows/release.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
||||
|
||||
- uses: google-github-actions/release-please-action@e0b9d1885d92e9a93d5ce8656de60e3b806e542c # v3
|
||||
- uses: google-github-actions/release-please-action@c078ea33917ab8cfa5300e48f4b7e6b16606aede # v3
|
||||
id: release
|
||||
with:
|
||||
command: manifest
|
||||
@@ -36,7 +36,6 @@ jobs:
|
||||
if: needs.release-please.outputs.releases_created == 'true'
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
needs:
|
||||
- release-please
|
||||
runs-on: ubuntu-latest
|
||||
@@ -49,6 +48,8 @@ jobs:
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Download Syft
|
||||
uses: anchore/sbom-action/download-syft@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4
|
||||
with:
|
||||
@@ -70,6 +71,7 @@ jobs:
|
||||
id-token: write
|
||||
env:
|
||||
IMAGE_TAG: ghcr.io/k8sgpt-ai/k8sgpt:${{ needs.release-please.outputs.tag_name }}
|
||||
IMAGE_NAME: k8sgpt
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
||||
@@ -98,11 +100,11 @@ jobs:
|
||||
${{ env.IMAGE_TAG }}
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: true
|
||||
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
|
||||
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_NAME }}
|
||||
cache-from: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
|
||||
cache-to: type=gha,scope=${{ github.ref_name }}-${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@448520c4f19577ffce70a8317e619089054687e3 # v0.13.4
|
||||
uses: anchore/sbom-action@422cb34a0f8b599678c41b21163ea6088edb2624 # v0.14.1
|
||||
with:
|
||||
image: ${{ env.IMAGE_TAG }}
|
||||
artifact-name: sbom-${{ env.IMAGE_NAME }}
|
||||
|
||||
56
.github/workflows/semantic_pr.yaml
vendored
Normal file
56
.github/workflows/semantic_pr.yaml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Semantic PR Validation
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: read # Needed for checking out the repository
|
||||
pull-requests: read # Needed for reading prs
|
||||
steps:
|
||||
- name: Validate Pull Request
|
||||
uses: amannn/action-semantic-pull-request@c3cd5d1ea3580753008872425915e343e351ab54 # v5.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
# Configure which types are allowed.
|
||||
# Default: https://github.com/commitizen/conventional-commit-types
|
||||
types: |
|
||||
feat
|
||||
fix
|
||||
build
|
||||
chore
|
||||
ci
|
||||
docs
|
||||
perf
|
||||
refactor
|
||||
revert
|
||||
style
|
||||
test
|
||||
deps
|
||||
scopes: |
|
||||
deps
|
||||
# Configure that a scope must always be provided.
|
||||
requireScope: false
|
||||
# When using "Squash and merge" on a PR with only one commit, GitHub
|
||||
# will suggest using that commit message instead of the PR title for the
|
||||
# merge commit, and it's easy to commit this by mistake. Enable this option
|
||||
# to also validate the commit message for one commit PRs.
|
||||
validateSingleCommit: true
|
||||
# Configure additional validation for the subject based on a regex.
|
||||
# This ensures the subject doesn't start with an uppercase character.
|
||||
subjectPattern: ^(?![A-Z]).+$
|
||||
# If `subjectPattern` is configured, you can use this property to override
|
||||
# the default error message that is shown when the pattern doesn't match.
|
||||
# The variables `subject` and `title` can be used within the message.
|
||||
subjectPatternError: |
|
||||
The subject "{subject}" found in the pull request title "{title}"
|
||||
didn't match the configured pattern. Please ensure that the subject
|
||||
doesn't start with an uppercase character.
|
||||
27
.github/workflows/test.yaml
vendored
Normal file
27
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.20"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
@@ -13,6 +13,29 @@ builds:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}}
|
||||
|
||||
nfpms:
|
||||
- file_name_template: '{{ .ProjectName }}_{{ .Arch }}'
|
||||
homepage: https://k8sgpt.ai
|
||||
description: >-
|
||||
K8sGPT is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english. It has SRE experience codified into it’s analyzers and helps to pull out the most relevant information to enrich it with AI.
|
||||
license: "MIT"
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
bindir: /usr/bin
|
||||
section: utils
|
||||
contents:
|
||||
- src: ./LICENSE
|
||||
dst: /usr/share/doc/nfpm/copyright
|
||||
file_info:
|
||||
mode: 0644
|
||||
|
||||
sboms:
|
||||
- artifacts: archive
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.0.4"}
|
||||
{".":"0.2.1"}
|
||||
436
CHANGELOG.md
436
CHANGELOG.md
@@ -1,5 +1,441 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.2.0...v0.2.1) (2023-04-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add anonymization example to README ([8a60b57](https://github.com/k8sgpt-ai/k8sgpt/commit/8a60b579409c67f092156ba1adf1be22cce37b8c))
|
||||
* add anonymization flag ([d2a84ea](https://github.com/k8sgpt-ai/k8sgpt/commit/d2a84ea2b5c800dd900aac3a48b1914bd9ddb917))
|
||||
* add more details on anonymize flag ([b687473](https://github.com/k8sgpt-ai/k8sgpt/commit/b687473e6169406002b0ee8be6ebb9ce43b46495))
|
||||
* add storage class names' check. ([c8ba7d6](https://github.com/k8sgpt-ai/k8sgpt/commit/c8ba7d62d2f1d262263d1dff8f980e91cdcd50e8))
|
||||
* improve documentation ([6f08654](https://github.com/k8sgpt-ai/k8sgpt/commit/6f0865413fc2854450d217225199cec199972490))
|
||||
* improve documentation & update hpa message ([11326c1](https://github.com/k8sgpt-ai/k8sgpt/commit/11326c1c5f307c718e8d1e56099537314ffedadd))
|
||||
* improve security of the MaskString function ([08f2a89](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2a89e54a65544322814286977b2c05acce89d))
|
||||
* initial impl of integration ([b0e5170](https://github.com/k8sgpt-ai/k8sgpt/commit/b0e517006e65ac2b4e2d4e2696531d4bbf62c34b))
|
||||
* initial impl of integration ([61d6e52](https://github.com/k8sgpt-ai/k8sgpt/commit/61d6e524657272cf3a967c724f212677fcfe7d2b))
|
||||
* integration ready for first review ([3682f5c](https://github.com/k8sgpt-ai/k8sgpt/commit/3682f5c7ebb9590e92162eed214a8127f71bcd81))
|
||||
* introduce StatefulSet analyser. ([c041ce2](https://github.com/k8sgpt-ai/k8sgpt/commit/c041ce2bbb4ecbc6f5637207c9f3071eee022744))
|
||||
* refactor integration to use Failure object ([c0afc0f](https://github.com/k8sgpt-ai/k8sgpt/commit/c0afc0f5c91cfa50b1f7af901800ff0a2b492d18))
|
||||
* return errors if filter specified by flag does not exist. ([dd5824f](https://github.com/k8sgpt-ai/k8sgpt/commit/dd5824f4365b01e3c501d8b5cda914dff138e03d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update kubernetes packages to v0.27.0 ([7a97034](https://github.com/k8sgpt-ai/k8sgpt/commit/7a97034cf41cb265111c752ee3d54fd90524ef59))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.7.0 ([#227](https://github.com/k8sgpt-ai/k8sgpt/issues/227)) ([5f3a5a5](https://github.com/k8sgpt-ai/k8sgpt/commit/5f3a5a54a02967acce40f8b4e9dd3a154c83f58c))
|
||||
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([fe261b3](https://github.com/k8sgpt-ai/k8sgpt/commit/fe261b375f4d7990906620f53ac26e792a34731b))
|
||||
* exit progressbar on error ([#99](https://github.com/k8sgpt-ai/k8sgpt/issues/99)) ([ab55f15](https://github.com/k8sgpt-ai/k8sgpt/commit/ab55f157ef026502d29eadf5ad83e917fe085a6c))
|
||||
* improve ReplaceIfMatch regex ([fd936ce](https://github.com/k8sgpt-ai/k8sgpt/commit/fd936ceaf725d1c1ed1f53eaa2204455dcd1e2af))
|
||||
* pdb test ([705d2a0](https://github.com/k8sgpt-ai/k8sgpt/commit/705d2a0dcebb63783782e06b6b775393daf1efb7))
|
||||
* use hpa namespace instead analyzer namespace ([#230](https://github.com/k8sgpt-ai/k8sgpt/issues/230)) ([a582d44](https://github.com/k8sgpt-ai/k8sgpt/commit/a582d444c5c53f25d7172947c690b35cad2cc176))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([b45ff1a](https://github.com/k8sgpt-ai/k8sgpt/commit/b45ff1aa8ef447df2b74bb8c6225e2f3d7c5bd63))
|
||||
* add statefulSet analyzer in the docs. ([#233](https://github.com/k8sgpt-ai/k8sgpt/issues/233)) ([ba01bd4](https://github.com/k8sgpt-ai/k8sgpt/commit/ba01bd4b6ecd64fbe249be54f20471afc6339208))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* add fakeai provider ([#218](https://github.com/k8sgpt-ai/k8sgpt/issues/218)) ([e449cb6](https://github.com/k8sgpt-ai/k8sgpt/commit/e449cb60230d440d5b8e00062db63de5d6d413bf))
|
||||
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([db1388f](https://github.com/k8sgpt-ai/k8sgpt/commit/db1388fd20dcf21069adcecd2796f2e1231162c8))
|
||||
* adding k8sgpt-approvers ([#238](https://github.com/k8sgpt-ai/k8sgpt/issues/238)) ([992b107](https://github.com/k8sgpt-ai/k8sgpt/commit/992b107c2d906663bb22998004a0859bccd45c77))
|
||||
* compiling successfully ([80ac51c](https://github.com/k8sgpt-ai/k8sgpt/commit/80ac51c804351226e1764e3e649ac56e22de3749))
|
||||
* **deps:** update anchore/sbom-action action to v0.14.1 ([#228](https://github.com/k8sgpt-ai/k8sgpt/issues/228)) ([9423b53](https://github.com/k8sgpt-ai/k8sgpt/commit/9423b53c1dbae3d0762420a0bacbdace9a2c18c9))
|
||||
* **deps:** update google-github-actions/release-please-action digest to c078ea3 ([a1d8012](https://github.com/k8sgpt-ai/k8sgpt/commit/a1d8012a5c748aee3f16621d6da9a0f0c8cba293))
|
||||
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([55dda43](https://github.com/k8sgpt-ai/k8sgpt/commit/55dda432ab89c4917bd28fceabcbe5569c0bf530))
|
||||
* **deps:** update google-github-actions/release-please-action digest to f7edb9e ([#241](https://github.com/k8sgpt-ai/k8sgpt/issues/241)) ([21dc61c](https://github.com/k8sgpt-ai/k8sgpt/commit/21dc61c04f4d772b5147b38a4d28e5dbddf5cdd8))
|
||||
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([428c348](https://github.com/k8sgpt-ai/k8sgpt/commit/428c3485868a7be95ea6776694e30b36badf4b5c))
|
||||
* fix mistake introduced by ab55f157 ([#240](https://github.com/k8sgpt-ai/k8sgpt/issues/240)) ([3845d47](https://github.com/k8sgpt-ai/k8sgpt/commit/3845d4747f4e0fc823d1bcf631d6ecdd5e4ccd03))
|
||||
* Fixing broken tests ([c809af3](https://github.com/k8sgpt-ai/k8sgpt/commit/c809af3f47388599fda3a88a4638feae1dc90492))
|
||||
* fixing filters ([258c69a](https://github.com/k8sgpt-ai/k8sgpt/commit/258c69a17c977867dfd0a7ad02727270b7c172e7))
|
||||
* fixing filters ([4d20f70](https://github.com/k8sgpt-ai/k8sgpt/commit/4d20f70fb40ff326ceb279f699068ec4956a2f10))
|
||||
* merged ([096321b](https://github.com/k8sgpt-ai/k8sgpt/commit/096321b31a6cf0d53b1861a3e4ad1efe84f697cc))
|
||||
* updated analysis_test.go ([825e9a4](https://github.com/k8sgpt-ai/k8sgpt/commit/825e9a43bd3ab7aa3ea52f315993cd778ea039e3))
|
||||
* updated link output ([1b7f4ce](https://github.com/k8sgpt-ai/k8sgpt/commit/1b7f4ce44a499e5389aec42fdee00bfa81ef0888))
|
||||
* updating based on feedback ([5e5d4b6](https://github.com/k8sgpt-ai/k8sgpt/commit/5e5d4b6de160dc7533067e1c0d8403c3faac1a9f))
|
||||
* weird new line after filter removed ([fabe01a](https://github.com/k8sgpt-ai/k8sgpt/commit/fabe01aa019f1db45ed2ff780f0d6d63297b230b))
|
||||
|
||||
## [0.2.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.8...v0.2.0) (2023-04-05)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* The format of the configuration file has changed. Users must update their configuration files to use the new format.
|
||||
|
||||
### Features
|
||||
|
||||
* add support for new configuration format ([9b243cd](https://github.com/k8sgpt-ai/k8sgpt/commit/9b243cdcaab1742fca2516bc1ae5710505e0eb65))
|
||||
* add tests for hpa analyzer ([5a59abb](https://github.com/k8sgpt-ai/k8sgpt/commit/5a59abb55d9e76f3095b903ea973138b1afdccf2))
|
||||
* add tests for ingress analyzer && Use t.Fatalf to report a fatal error if RunAnalysis returns an unexpected error ([e27e940](https://github.com/k8sgpt-ai/k8sgpt/commit/e27e9409dcaad78eefd79659e91364617407ae59))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.6.1 ([#207](https://github.com/k8sgpt-ai/k8sgpt/issues/207)) ([eeac731](https://github.com/k8sgpt-ai/k8sgpt/commit/eeac731858999f6f462a7b6ccf210af603674b30))
|
||||
* **deps:** update module github.com/spf13/cobra to v1.7.0 ([5d5e082](https://github.com/k8sgpt-ai/k8sgpt/commit/5d5e082f417905954be33b3a620efef674f2588d))
|
||||
* **deps:** update module github.com/stretchr/testify to v1.8.2 ([f5e3ca0](https://github.com/k8sgpt-ai/k8sgpt/commit/f5e3ca0bcab9325145a2e1d8624f585ffee8e29f))
|
||||
* **deps:** update module golang.org/x/term to v0.7.0 ([8ab3573](https://github.com/k8sgpt-ai/k8sgpt/commit/8ab3573e13a3e3ab98e6f0aa76e429117c888f7f))
|
||||
* details in json output ([2f21002](https://github.com/k8sgpt-ai/k8sgpt/commit/2f2100289953af7820bbb01f2c980cf5492de079))
|
||||
* fixed hpa tests after rebase ([a24d1f1](https://github.com/k8sgpt-ai/k8sgpt/commit/a24d1f1b304e9448e63c1b7fc283b4cc8bc639aa))
|
||||
* regression on dynamic filters ([93bcc62](https://github.com/k8sgpt-ai/k8sgpt/commit/93bcc627ba64a9139e65290a8512e0a9b4bf1a69))
|
||||
* Spelling ([ba4d701](https://github.com/k8sgpt-ai/k8sgpt/commit/ba4d7016814ce97353e98658d5bbcd692007e4a9))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add curl command and release-please annoations ([1849209](https://github.com/k8sgpt-ai/k8sgpt/commit/184920988f7da928cca7fae4a676e4ee5f13cad1))
|
||||
* add guide to details block ([ddc120e](https://github.com/k8sgpt-ai/k8sgpt/commit/ddc120e7c2657385737d3490def28dbabdd2242d))
|
||||
* add installation guide via packages ([8e4ce6a](https://github.com/k8sgpt-ai/k8sgpt/commit/8e4ce6a974813258fb9cbeabbcaa3b8f6966a748))
|
||||
* minor change ([53c1330](https://github.com/k8sgpt-ai/k8sgpt/commit/53c13305383eb454fe45fefa3483cef4821d5d34))
|
||||
* modify README ([fc47c58](https://github.com/k8sgpt-ai/k8sgpt/commit/fc47c58ae1c2b5511ebbe0ed35714e4ecbb4bb7a))
|
||||
* modify README ([0f46ceb](https://github.com/k8sgpt-ai/k8sgpt/commit/0f46ceb4456a90e7e05aeff23d25d5775bbf9c2b))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* added initial tests for json output ([22e3166](https://github.com/k8sgpt-ai/k8sgpt/commit/22e31661bff27b28339898826a34ffdcfcff3583))
|
||||
* analyzer and ai interfacing ([#200](https://github.com/k8sgpt-ai/k8sgpt/issues/200)) ([0195bfa](https://github.com/k8sgpt-ai/k8sgpt/commit/0195bfab30ab748b3bb7f1b8c8f0e988b99ee54d))
|
||||
* **deps:** pin anchore/sbom-action action to 448520c ([#203](https://github.com/k8sgpt-ai/k8sgpt/issues/203)) ([9ff3fbc](https://github.com/k8sgpt-ai/k8sgpt/commit/9ff3fbc382ed78b55a7a1966ecdae186c03b2848))
|
||||
* **deps:** update golang docker tag to v1.20.3 ([e9994b8](https://github.com/k8sgpt-ai/k8sgpt/commit/e9994b8d167d4f1d9c0d0dabf8385ff22cfd16a4))
|
||||
* made json output prettier and improved output ([db40734](https://github.com/k8sgpt-ai/k8sgpt/commit/db40734a0db89850a2a685c9a7f5f5559875b7b3))
|
||||
|
||||
## [0.1.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.7...v0.1.8) (2023-04-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add password flag for backend authentication ([#199](https://github.com/k8sgpt-ai/k8sgpt/issues/199)) ([075a940](https://github.com/k8sgpt-ai/k8sgpt/commit/075a940d2c9bdd8aa9162940ed46abad47d46998))
|
||||
* adding shields to readme ([213ecd8](https://github.com/k8sgpt-ai/k8sgpt/commit/213ecd8e83933fabaa5d3d674c67958599dd72ce))
|
||||
* adding unit testing and example ([35b838b](https://github.com/k8sgpt-ai/k8sgpt/commit/35b838bfafa248dbf3932c7a3ee708b1a1539f18))
|
||||
* alias filter to filters ([dde4e83](https://github.com/k8sgpt-ai/k8sgpt/commit/dde4e833b0e87553dea4e5c1e17a14e303956bc1))
|
||||
* analyzer ifacing ([426f562](https://github.com/k8sgpt-ai/k8sgpt/commit/426f562be83ed0e708a07b9e1900ac06fa017c27))
|
||||
* service test ([44cc8f7](https://github.com/k8sgpt-ai/k8sgpt/commit/44cc8f7ad68d152ec577e57cab7d8d9ab9613378))
|
||||
* test workflow ([5f30a4d](https://github.com/k8sgpt-ai/k8sgpt/commit/5f30a4ddf44ebff949bb0573f261667539a2dcfb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.8 ([91fb065](https://github.com/k8sgpt-ai/k8sgpt/commit/91fb06530a21259da6e72c28342e743d2b481294))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* create linux packages ([#201](https://github.com/k8sgpt-ai/k8sgpt/issues/201)) ([67753be](https://github.com/k8sgpt-ai/k8sgpt/commit/67753be6f317c462ebe1d9a316f2b0c9684ca4e5))
|
||||
* **deps:** pin dependencies ([#198](https://github.com/k8sgpt-ai/k8sgpt/issues/198)) ([f8291aa](https://github.com/k8sgpt-ai/k8sgpt/commit/f8291aab085209f9fee13a6c92c96076163e2e90))
|
||||
|
||||
## [0.1.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.6...v0.1.7) (2023-04-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add hpa analyzer and init additionalAnalyzers ([3603872](https://github.com/k8sgpt-ai/k8sgpt/commit/360387249feb9a999286aaa874a13007986219a5))
|
||||
* add pda analyzer ([532a5ce](https://github.com/k8sgpt-ai/k8sgpt/commit/532a5ce0332a8466df42bc944800e6668e349801))
|
||||
* check if ScaleTargetRef is possible option ([5dad75f](https://github.com/k8sgpt-ai/k8sgpt/commit/5dad75fbe9fd15cfa7bfa69c046b851ea905876f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* hpaAnalyzer analysis result is using wrong parent ([1190fe6](https://github.com/k8sgpt-ai/k8sgpt/commit/1190fe60fdd6e66ce435874628039df7047a52b9))
|
||||
* spelling of PodDisruptionBudget ([ceff008](https://github.com/k8sgpt-ai/k8sgpt/commit/ceff0084df1b6de16f1ed503ee8a4b3c1a9f8648))
|
||||
* update client API call to use StatefulSet instead of Deployment ([4916fef](https://github.com/k8sgpt-ai/k8sgpt/commit/4916fef9d6b75c54bcfbc5d136550018e96e3632))
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* merged main into branch ([3e836d8](https://github.com/k8sgpt-ai/k8sgpt/commit/3e836d81b7c33ce5c0c133c2e1ca3b0c8d3eeeb0)), closes [#101](https://github.com/k8sgpt-ai/k8sgpt/issues/101)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update anchore/sbom-action action to v0.14.1 ([80f29da](https://github.com/k8sgpt-ai/k8sgpt/commit/80f29dae4fd6f6348967192ce2f51f0e0fb5dea0))
|
||||
* merge branch 'chetanguptaa-some-fixes' ([071ee56](https://github.com/k8sgpt-ai/k8sgpt/commit/071ee560f36b64b4c65274181e2d13bb14d5b914))
|
||||
* refine renovate config ([#172](https://github.com/k8sgpt-ai/k8sgpt/issues/172)) ([d23da9a](https://github.com/k8sgpt-ai/k8sgpt/commit/d23da9ae836a07f0fd59c20a1c3c71d6b7f75277))
|
||||
* removes bar on normal analyze events ([e1d8992](https://github.com/k8sgpt-ai/k8sgpt/commit/e1d89920b097db4417c55b020fb23dd8cbaf19ed))
|
||||
* removes bar on normal analyze events ([96d0d75](https://github.com/k8sgpt-ai/k8sgpt/commit/96d0d754eab67c0742d3a36a1eefb9c28df59e96))
|
||||
* update dependencies ([#174](https://github.com/k8sgpt-ai/k8sgpt/issues/174)) ([9d9c262](https://github.com/k8sgpt-ai/k8sgpt/commit/9d9c26214fbb4c4faba7ef85f2204bc961396de8))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add pdbAnalyzer as optional analyzer ([f6974d0](https://github.com/k8sgpt-ai/k8sgpt/commit/f6974d07581384e260059f121242854320dfc58b))
|
||||
|
||||
## [0.1.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.5...v0.1.6) (2023-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* analysis detail not displayed when --explain ([869ba90](https://github.com/k8sgpt-ai/k8sgpt/commit/869ba909075a5543413fb6ae7fc79aa067c08da4))
|
||||
|
||||
## [0.1.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.4...v0.1.5) (2023-03-31)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add & remove default filter(s) to analyze. ([32ddf66](https://github.com/k8sgpt-ai/k8sgpt/commit/32ddf6691ce083fd4283a1d5ac4b9f02e90df867))
|
||||
* add filter command add "list" subcommand ([#159](https://github.com/k8sgpt-ai/k8sgpt/issues/159)) ([6e17c9e](https://github.com/k8sgpt-ai/k8sgpt/commit/6e17c9e285e3871bb8f694b734a8cd6fd02e60f0))
|
||||
* check if filters does not empty on add & remove ([975813d](https://github.com/k8sgpt-ai/k8sgpt/commit/975813d3284719c877630ad20f90c6fe163283da))
|
||||
* remove filter prefix on subcommand ([30faf84](https://github.com/k8sgpt-ai/k8sgpt/commit/30faf842541c0be6b6483f71f6cf04d5cafecef5))
|
||||
* rework filters ([3ed545f](https://github.com/k8sgpt-ai/k8sgpt/commit/3ed545f33fb3ecb3827c03e8c89027c61386c44f))
|
||||
* update filters add & remove to be more consistent ([9aa0e89](https://github.com/k8sgpt-ai/k8sgpt/commit/9aa0e8960ee340208b4749954c99867842ba58b9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* kubecontext flag has no effect ([a8bf451](https://github.com/k8sgpt-ai/k8sgpt/commit/a8bf45134ff3a72dc3e531d720f119790faff9d4))
|
||||
* spelling on dupplicateFilters ([0a12448](https://github.com/k8sgpt-ai/k8sgpt/commit/0a124484a23789376258413e73628c7b1d7abded))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* renamed filter list file ([25f8dc3](https://github.com/k8sgpt-ai/k8sgpt/commit/25f8dc390cccd66965993f464351e671af11f8ac))
|
||||
|
||||
## [0.1.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.3...v0.1.4) (2023-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Ingress class validation ([#154](https://github.com/k8sgpt-ai/k8sgpt/issues/154)) ([b061566](https://github.com/k8sgpt-ai/k8sgpt/commit/b061566404ef80288ca29add2d401574109d44c0))
|
||||
* output selected backend ([#153](https://github.com/k8sgpt-ai/k8sgpt/issues/153)) ([be061da](https://github.com/k8sgpt-ai/k8sgpt/commit/be061da5b65045938acd70ad2eb2d21b87d2d6bf))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* now supports different kubeconfig and kubectx ([c8f3c94](https://github.com/k8sgpt-ai/k8sgpt/commit/c8f3c946b00c00cd185961a4fa777806da94014e))
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* removed sample flag ([0afd528](https://github.com/k8sgpt-ai/k8sgpt/commit/0afd52844b96579391f77698bf0555145b6d2be8))
|
||||
|
||||
## [0.1.3](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.2...v0.1.3) (2023-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add secret validation to ingress analyzer ([#141](https://github.com/k8sgpt-ai/k8sgpt/issues/141)) ([86c7e81](https://github.com/k8sgpt-ai/k8sgpt/commit/86c7e81e18db02ebcbfe35d470682c982871375f))
|
||||
* bugfix for output ([2eab0c5](https://github.com/k8sgpt-ai/k8sgpt/commit/2eab0c544fbb6026f6aea79b08d8f29c061acf2e))
|
||||
* CODE_OF_CONDUCT.md ([#129](https://github.com/k8sgpt-ai/k8sgpt/issues/129)) ([fe73633](https://github.com/k8sgpt-ai/k8sgpt/commit/fe73633273c5c1f4188bca48471283535967d5aa))
|
||||
* create-security.md ([27b8916](https://github.com/k8sgpt-ai/k8sgpt/commit/27b8916f297570907437686c6d958636fb249d50))
|
||||
* improvement to analysis speed ([548039e](https://github.com/k8sgpt-ai/k8sgpt/commit/548039ebe62bb609c1aa288e5e49845850fd2dd8))
|
||||
* init ingress analyzer ([#138](https://github.com/k8sgpt-ai/k8sgpt/issues/138)) ([fe683b7](https://github.com/k8sgpt-ai/k8sgpt/commit/fe683b71b84fe82459b0ffe366b4dcfa1c978cfe))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add Ingress in GetParent switch case ([14ba8d5](https://github.com/k8sgpt-ai/k8sgpt/commit/14ba8d555005f31fc2201cb8b61653093c19b8a7))
|
||||
* bugfix for output ([#148](https://github.com/k8sgpt-ai/k8sgpt/issues/148)) ([172c2df](https://github.com/k8sgpt-ai/k8sgpt/commit/172c2df6c55f5fddbfec7f8526be5f2323d1b900))
|
||||
* Change ObjectMeta value in Ingress analyser. ([bf49a51](https://github.com/k8sgpt-ai/k8sgpt/commit/bf49a51c62af450cff51a590547ef30989bd2e93))
|
||||
* typo in description of the filter flag in analyze command ([#147](https://github.com/k8sgpt-ai/k8sgpt/issues/147)) ([f4765be](https://github.com/k8sgpt-ai/k8sgpt/commit/f4765bed1b1ad121a81b35878fdb866354b5e34a))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update google-github-actions/release-please-action digest to ee9822e ([#132](https://github.com/k8sgpt-ai/k8sgpt/issues/132)) ([01b2826](https://github.com/k8sgpt-ai/k8sgpt/commit/01b282647512a4eaebd42ab5847b5534de148d14))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add new slack link ([#134](https://github.com/k8sgpt-ai/k8sgpt/issues/134)) ([#135](https://github.com/k8sgpt-ai/k8sgpt/issues/135)) ([cad2b38](https://github.com/k8sgpt-ai/k8sgpt/commit/cad2b38d037658495024ec0166ebd3e936f65c2e))
|
||||
|
||||
## [0.1.2](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.1...v0.1.2) (2023-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added namespace filter ([#127](https://github.com/k8sgpt-ai/k8sgpt/issues/127)) ([b78ab3d](https://github.com/k8sgpt-ai/k8sgpt/commit/b78ab3d9b503a256bf6ccf18276e20140ae17d1c))
|
||||
* prefix templates ([#125](https://github.com/k8sgpt-ai/k8sgpt/issues/125)) ([65a568e](https://github.com/k8sgpt-ai/k8sgpt/commit/65a568e937a8fdacc179f5e8b1a021a0178c04f0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* readme code blocks ([#126](https://github.com/k8sgpt-ai/k8sgpt/issues/126)) ([c8b92aa](https://github.com/k8sgpt-ai/k8sgpt/commit/c8b92aaa0e2795aa8d65f84277c8adfe0f1d14e3))
|
||||
* update README.md ([#119](https://github.com/k8sgpt-ai/k8sgpt/issues/119)) ([05abe97](https://github.com/k8sgpt-ai/k8sgpt/commit/05abe975dd859cd85096a1a7182f17b0437ad20f))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* added default issue template ([#96](https://github.com/k8sgpt-ai/k8sgpt/issues/96)) ([#121](https://github.com/k8sgpt-ai/k8sgpt/issues/121)) ([11c227b](https://github.com/k8sgpt-ai/k8sgpt/commit/11c227b82e16dac8b46cbd03bb04d9cc1c2b5ac3))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add new issue templates ([dbd305f](https://github.com/k8sgpt-ai/k8sgpt/commit/dbd305f901cca09b7148254c3aa7a7435504d6cc))
|
||||
* add WSL gcc instructions ([4d5566b](https://github.com/k8sgpt-ai/k8sgpt/commit/4d5566b4df7aedf43edbeeb03130f0ba77dbed1a))
|
||||
* added Windows and Linux instalation steps in README ([#116](https://github.com/k8sgpt-ai/k8sgpt/issues/116)) ([3bfb278](https://github.com/k8sgpt-ai/k8sgpt/commit/3bfb278f81a9c550ee37a88c0cb0377331802542))
|
||||
* fix indentations ([a46416d](https://github.com/k8sgpt-ai/k8sgpt/commit/a46416dce0f5cee2d42b27525023b04af1a8e3c0))
|
||||
* rename ISSUE_TEMPLATE ([#124](https://github.com/k8sgpt-ai/k8sgpt/issues/124)) ([cb4932c](https://github.com/k8sgpt-ai/k8sgpt/commit/cb4932c39df4903a4b48ae5f0428860027f76fd2))
|
||||
|
||||
## [0.1.1](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.1.0...v0.1.1) (2023-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* this stops service exiting the program ([6f90386](https://github.com/k8sgpt-ai/k8sgpt/commit/6f90386fc93b2e39e59832468922e8ba7210b8e5))
|
||||
* updated readme ([e0141d1](https://github.com/k8sgpt-ai/k8sgpt/commit/e0141d1cf54b5b37b25a5caeb9d5c940b9410ea7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* short term solution for exhaustion ([5890e3a](https://github.com/k8sgpt-ai/k8sgpt/commit/5890e3a79c80a2973af2feb7d50e7f9c57c563c2))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* update README.md ([93b947f](https://github.com/k8sgpt-ai/k8sgpt/commit/93b947f261e401c10dde6dc1854e6e22187437d6))
|
||||
* update root.go path ([2cb1c9c](https://github.com/k8sgpt-ai/k8sgpt/commit/2cb1c9c150d052bb3942d9f62ded9d54b0e1873e))
|
||||
|
||||
## [0.1.0](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.9...v0.1.0) (2023-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added british alias ([39c0444](https://github.com/k8sgpt-ai/k8sgpt/commit/39c0444fac9b46d0faa347b45df779b97019e5b6)), closes [#93](https://github.com/k8sgpt-ai/k8sgpt/issues/93)
|
||||
* enables overwriting of cache ([#95](https://github.com/k8sgpt-ai/k8sgpt/issues/95)) ([a270f7c](https://github.com/k8sgpt-ai/k8sgpt/commit/a270f7c89fb8bec35984715c5e4d160a2307e678))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* add CODEOWNERS ([c5c6162](https://github.com/k8sgpt-ai/k8sgpt/commit/c5c6162df1f3701659e47bce6e9fc6e3c569e539))
|
||||
* add codeowners file ([#102](https://github.com/k8sgpt-ai/k8sgpt/issues/102)) ([829ff56](https://github.com/k8sgpt-ai/k8sgpt/commit/829ff566c0a964250d3d8d45306d410e1b9d9d35))
|
||||
* release 0.1.0 ([f9c7daf](https://github.com/k8sgpt-ai/k8sgpt/commit/f9c7daf3dcd06dcd9cea5603108b8a42ee273348))
|
||||
|
||||
## [0.0.9](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.8...v0.0.9) (2023-03-28)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* small update ([202e8e2](https://github.com/k8sgpt-ai/k8sgpt/commit/202e8e2977422b2b4506a80dc9b76a392c5457eb))
|
||||
|
||||
## [0.0.8](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.7...v0.0.8) (2023-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add generation of api-keys to cli ([#87](https://github.com/k8sgpt-ai/k8sgpt/issues/87)) ([1c653ec](https://github.com/k8sgpt-ai/k8sgpt/commit/1c653ecc51b74a2f51ce7240ffaee0fe75f2e8dd))
|
||||
* add generation of api-keys to cli ([#87](https://github.com/k8sgpt-ai/k8sgpt/issues/87)) ([bb2db5c](https://github.com/k8sgpt-ai/k8sgpt/commit/bb2db5ca7923e2049308d1674bb59ae8154e415c))
|
||||
* addition of simple language support ([c3008c5](https://github.com/k8sgpt-ai/k8sgpt/commit/c3008c5e75acbb35d864135199ca9c034f59e35f))
|
||||
* version ([0c231d6](https://github.com/k8sgpt-ai/k8sgpt/commit/0c231d635e7ad71609bb80abac5e0ade15ffb860))
|
||||
* version ([931f072](https://github.com/k8sgpt-ai/k8sgpt/commit/931f072e0ab0cfd77f261b0b719cf0819f85b951))
|
||||
|
||||
## [0.0.7](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.6...v0.0.7) (2023-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* wip fixing missing details ([0852c65](https://github.com/k8sgpt-ai/k8sgpt/commit/0852c658ded33b91e1d323bd8cba6ac6935cb525))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* moved code ([a194d4a](https://github.com/k8sgpt-ai/k8sgpt/commit/a194d4a509329cbc5a00724b0a19c75726c2a0d3))
|
||||
* return success on no issues ([009f47c](https://github.com/k8sgpt-ai/k8sgpt/commit/009f47c8e8ee6d3ce9b36110c36edae97690c949))
|
||||
* updated readme ([06fb807](https://github.com/k8sgpt-ai/k8sgpt/commit/06fb8073dc5b0b5bd9f8d115d9ec206ab238d68f))
|
||||
|
||||
## [0.0.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.6...v0.0.6) (2023-03-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add service analysis ([961fb6c](https://github.com/k8sgpt-ai/k8sgpt/commit/961fb6c555f59f1276531f462739b76b1508830e))
|
||||
* added analysis for pvcs ([88d49ae](https://github.com/k8sgpt-ai/k8sgpt/commit/88d49ae21c7d889d59361de157360f80503683be))
|
||||
* also fixes bug if the events feed is empty ([#73](https://github.com/k8sgpt-ai/k8sgpt/issues/73)) ([a1093dc](https://github.com/k8sgpt-ai/k8sgpt/commit/a1093dcfe468a7671c9e543372f73780fb38418e))
|
||||
* build container ([260640f](https://github.com/k8sgpt-ai/k8sgpt/commit/260640f865baefba8ac256f800d4992f25ca15fd))
|
||||
* find parent objects ([b29c6e4](https://github.com/k8sgpt-ai/k8sgpt/commit/b29c6e45825807d07dd6fdb954457772f40b1b0e))
|
||||
* find parent objects and add information about them ([#72](https://github.com/k8sgpt-ai/k8sgpt/issues/72)) ([14e85b0](https://github.com/k8sgpt-ai/k8sgpt/commit/14e85b08ff7d9a571796905260db7f1056b6e838))
|
||||
* find replicaset errors ([8ac56e0](https://github.com/k8sgpt-ai/k8sgpt/commit/8ac56e062baef2a0cf7c7ce2b4c97753f079f157))
|
||||
* initial json implementation ([#68](https://github.com/k8sgpt-ai/k8sgpt/issues/68)) ([979f13f](https://github.com/k8sgpt-ai/k8sgpt/commit/979f13f043f54a5bc74d0a49fee0db2faaf0a4f8))
|
||||
* interfaced out ai clients ([90b3c08](https://github.com/k8sgpt-ai/k8sgpt/commit/90b3c0898c8ab1299ce8b60effe981f5fc9ed63b))
|
||||
* support for multi-auth ([51aa59a](https://github.com/k8sgpt-ai/k8sgpt/commit/51aa59aea8c0fd5533d2300c7a79c0b9008ef887))
|
||||
* updated readme ([7336924](https://github.com/k8sgpt-ai/k8sgpt/commit/73369240b4fc8c91dae0ae272e671f7b413e3bdc))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add permissions to read repository ([d6cc4cf](https://github.com/k8sgpt-ai/k8sgpt/commit/d6cc4cfcbffbf84f27c7e4e4159da1e42dd5d689))
|
||||
* build ([1fbed3e](https://github.com/k8sgpt-ai/k8sgpt/commit/1fbed3e44ff790fccfef502ddafae92e34629c21))
|
||||
* container naming ([115276e](https://github.com/k8sgpt-ai/k8sgpt/commit/115276e01a38fc1692d6b66ab56a33f1e1793974))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.5 ([105fe44](https://github.com/k8sgpt-ai/k8sgpt/commit/105fe44680e5a987d4a65ff9c58b5b2211808c5e))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.6 ([37a1d3f](https://github.com/k8sgpt-ai/k8sgpt/commit/37a1d3f47e07caddb168f228627973870a9d867e))
|
||||
* **deps:** update module github.com/sashabaranov/go-openai to v1.5.7 ([7f7726d](https://github.com/k8sgpt-ai/k8sgpt/commit/7f7726d59a63baeaf8ff110e00b30a20ec7f1df5))
|
||||
* minor adaptions ([ef17b84](https://github.com/k8sgpt-ai/k8sgpt/commit/ef17b845ba3c65c16ed5dcc417e3e3d3d40dd04e))
|
||||
* missing parent when explain is used ([9c7d559](https://github.com/k8sgpt-ai/k8sgpt/commit/9c7d55955b777ad201307cb946e0fc81cf9c4b99))
|
||||
* release please config ([c402c7b](https://github.com/k8sgpt-ai/k8sgpt/commit/c402c7bab7baababbbc7c82965d8337de7d50d35))
|
||||
* remove sboms from goreleaser ([addc01f](https://github.com/k8sgpt-ai/k8sgpt/commit/addc01f700dd2ea31ec24dcf4995bb7ed4a4785e))
|
||||
* semantic commit token permission ([#69](https://github.com/k8sgpt-ai/k8sgpt/issues/69)) ([0181c0a](https://github.com/k8sgpt-ai/k8sgpt/commit/0181c0aeb56ad82fd232ce1c7788c43b7bd03bf2))
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
* add some important information to contributing ([9ab7f58](https://github.com/k8sgpt-ai/k8sgpt/commit/9ab7f587620d69e4e8fc98faabce6417c35f7497))
|
||||
* update CONTRIBUTING ([05a787d](https://github.com/k8sgpt-ai/k8sgpt/commit/05a787d53dfe5e625c6449ac1e21ec36e66ddd28))
|
||||
* update CONTRIBUTING ([26449e1](https://github.com/k8sgpt-ai/k8sgpt/commit/26449e10efd8926cccd4a2eaa4e9dc3afa8bd01a))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* add bot secret to goreleaser ([171e58b](https://github.com/k8sgpt-ai/k8sgpt/commit/171e58b51107f75717694e35c4e249ee41f0409a))
|
||||
* add brew tap generation on release ([2992c4e](https://github.com/k8sgpt-ai/k8sgpt/commit/2992c4e5c8abad50c90ed85523c732f19ab1f31c))
|
||||
* add initial renovate config ([e37dbc7](https://github.com/k8sgpt-ai/k8sgpt/commit/e37dbc7909f1c520c4c6660c25b45de5847ea581))
|
||||
* add pull request template ([a6d5132](https://github.com/k8sgpt-ai/k8sgpt/commit/a6d5132b8c2ff077680e2edfd8361a93008197fd))
|
||||
* add release-please ([da7b409](https://github.com/k8sgpt-ai/k8sgpt/commit/da7b40978d55a6afed4c3a1ca83a756238feaca8))
|
||||
* add semantic pr validation ([#66](https://github.com/k8sgpt-ai/k8sgpt/issues/66)) ([ad594c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ad594c7cb2105e0eff72d1767b2ddcc4dc0e3d38))
|
||||
* change module repo ([a307c13](https://github.com/k8sgpt-ai/k8sgpt/commit/a307c132b3464ff2e949c8a5588e01d344de91a0))
|
||||
* **deps:** pin amannn/action-semantic-pull-request action to c3cd5d1 ([3621766](https://github.com/k8sgpt-ai/k8sgpt/commit/36217667ceb87d9b97b44dc91e0ff6e7a1b86e14))
|
||||
* **deps:** pin dependencies ([f6072f5](https://github.com/k8sgpt-ai/k8sgpt/commit/f6072f56cbe2c073b7b7ebef6c12fa98120e54e2))
|
||||
* **deps:** pin dependencies ([5b360de](https://github.com/k8sgpt-ai/k8sgpt/commit/5b360de2ae6094cf850a4ae973a22855c21a9040))
|
||||
* **deps:** pin dependencies ([7fea7d1](https://github.com/k8sgpt-ai/k8sgpt/commit/7fea7d14a572fe0fd05f5f241b98e93655fb1965))
|
||||
* **deps:** update actions/checkout digest to 8f4b7f8 ([9955d75](https://github.com/k8sgpt-ai/k8sgpt/commit/9955d754505b60f28d17397132a1d02e95ffe303))
|
||||
* **main:** release 0.0.3 ([53c9947](https://github.com/k8sgpt-ai/k8sgpt/commit/53c994725ea2c2c54898ffe5307d9df40e9c1fe5))
|
||||
* **main:** release 0.0.3 ([f5d8609](https://github.com/k8sgpt-ai/k8sgpt/commit/f5d86092f49faef8d71cb950986d76c3f92daf46))
|
||||
* **main:** release 0.0.3 ([22873a6](https://github.com/k8sgpt-ai/k8sgpt/commit/22873a67163e58484d2a0ad343b4ba3c83e51d8f))
|
||||
* **main:** release 0.0.4 ([13b7d58](https://github.com/k8sgpt-ai/k8sgpt/commit/13b7d58e590078f086a0af2f9d1800e0e65a28bb))
|
||||
* **main:** release 0.0.4 ([aef7256](https://github.com/k8sgpt-ai/k8sgpt/commit/aef7256dc3a85817573744f8b4a54f834368bac7))
|
||||
* **main:** release 0.0.4 ([6dbcde9](https://github.com/k8sgpt-ai/k8sgpt/commit/6dbcde94e961a6e5a1fc0559d2a1da5567a659de))
|
||||
* **main:** release 0.0.5 ([9fecc1e](https://github.com/k8sgpt-ai/k8sgpt/commit/9fecc1ea6df4104412fc1230372de6f26aa1ade2))
|
||||
* **main:** release 0.0.6 ([d554bba](https://github.com/k8sgpt-ai/k8sgpt/commit/d554bba38494745f83b5a8931f665429af35a31a))
|
||||
* release 0.0.3 ([4840aa0](https://github.com/k8sgpt-ai/k8sgpt/commit/4840aa081e3aa4a7a01fd3fd5f837fa6f0c3c02c))
|
||||
* release 0.0.3 ([de02795](https://github.com/k8sgpt-ai/k8sgpt/commit/de027955ea18a751c5f991e7ff0f60b90ae704b0))
|
||||
* release 0.0.3 ([a927c32](https://github.com/k8sgpt-ai/k8sgpt/commit/a927c32def806bb8b99e1cfcd4ee3dcdeca6ae5d))
|
||||
* release 0.0.4 ([08f2c31](https://github.com/k8sgpt-ai/k8sgpt/commit/08f2c3112e2cc16b49b9cf8fdbd97368acecc754))
|
||||
* release 0.0.5 ([8da8945](https://github.com/k8sgpt-ai/k8sgpt/commit/8da8945d1b8d898440be235f88bdb2c08b0f9f84))
|
||||
* release 0.0.6 ([dc2bfa9](https://github.com/k8sgpt-ai/k8sgpt/commit/dc2bfa918c080a6c1b2e5ef66d699d9e08e28e10))
|
||||
|
||||
## [0.0.6](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.5...v0.0.6) (2023-03-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add service analysis ([961fb6c](https://github.com/k8sgpt-ai/k8sgpt/commit/961fb6c555f59f1276531f462739b76b1508830e))
|
||||
* added analysis for pvcs ([88d49ae](https://github.com/k8sgpt-ai/k8sgpt/commit/88d49ae21c7d889d59361de157360f80503683be))
|
||||
* also fixes bug if the events feed is empty ([#73](https://github.com/k8sgpt-ai/k8sgpt/issues/73)) ([a1093dc](https://github.com/k8sgpt-ai/k8sgpt/commit/a1093dcfe468a7671c9e543372f73780fb38418e))
|
||||
* find parent objects ([b29c6e4](https://github.com/k8sgpt-ai/k8sgpt/commit/b29c6e45825807d07dd6fdb954457772f40b1b0e))
|
||||
* find parent objects and add information about them ([#72](https://github.com/k8sgpt-ai/k8sgpt/issues/72)) ([14e85b0](https://github.com/k8sgpt-ai/k8sgpt/commit/14e85b08ff7d9a571796905260db7f1056b6e838))
|
||||
* initial json implementation ([#68](https://github.com/k8sgpt-ai/k8sgpt/issues/68)) ([979f13f](https://github.com/k8sgpt-ai/k8sgpt/commit/979f13f043f54a5bc74d0a49fee0db2faaf0a4f8))
|
||||
* interfaced out ai clients ([90b3c08](https://github.com/k8sgpt-ai/k8sgpt/commit/90b3c0898c8ab1299ce8b60effe981f5fc9ed63b))
|
||||
* support for multi-auth ([51aa59a](https://github.com/k8sgpt-ai/k8sgpt/commit/51aa59aea8c0fd5533d2300c7a79c0b9008ef887))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* missing parent when explain is used ([9c7d559](https://github.com/k8sgpt-ai/k8sgpt/commit/9c7d55955b777ad201307cb946e0fc81cf9c4b99))
|
||||
* semantic commit token permission ([#69](https://github.com/k8sgpt-ai/k8sgpt/issues/69)) ([0181c0a](https://github.com/k8sgpt-ai/k8sgpt/commit/0181c0aeb56ad82fd232ce1c7788c43b7bd03bf2))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* add semantic pr validation ([#66](https://github.com/k8sgpt-ai/k8sgpt/issues/66)) ([ad594c7](https://github.com/k8sgpt-ai/k8sgpt/commit/ad594c7cb2105e0eff72d1767b2ddcc4dc0e3d38))
|
||||
* **deps:** pin amannn/action-semantic-pull-request action to c3cd5d1 ([3621766](https://github.com/k8sgpt-ai/k8sgpt/commit/36217667ceb87d9b97b44dc91e0ff6e7a1b86e14))
|
||||
|
||||
## [0.0.5](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.4...v0.0.5) (2023-03-24)
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* release 0.0.5 ([8da8945](https://github.com/k8sgpt-ai/k8sgpt/commit/8da8945d1b8d898440be235f88bdb2c08b0f9f84))
|
||||
|
||||
## [0.0.4](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.0.4...v0.0.4) (2023-03-24)
|
||||
|
||||
|
||||
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@k8sgpt.ai.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
280
README.md
280
README.md
@@ -2,45 +2,291 @@
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./images/banner-white.png" width="600px;">
|
||||
<img alt="Text changing depending on mode. Light: 'So light!' Dark: 'So dark!'" src="./images/banner-black.png" width="600px;">
|
||||
</picture>
|
||||
<br/>
|
||||
|
||||
_Try it out now_
|
||||

|
||||

|
||||

|
||||
|
||||
`k8sgpt` is a tool for scanning your Kubernetes clusters, diagnosing, and triaging issues in simple English.
|
||||
|
||||
It has SRE experience codified into its analyzers and helps to pull out the most relevant information to enrich it with AI.
|
||||
|
||||
# Installation
|
||||
|
||||
|
||||
## Linux/Mac via brew
|
||||
|
||||
```
|
||||
brew tap k8sgpt-ai/k8sgpt
|
||||
brew install k8sgpt
|
||||
```
|
||||
|
||||
`k8sgpt` is a tool for scanning your kubernetes clusters, diagnosing and triaging issues in simple english.
|
||||
<details>
|
||||
<summary>RPM-based installation (RedHat/CentOS/Fedora)</summary>
|
||||
|
||||
It has SRE experience codified into it's analyzers and helps to pull out the most relevent information to enrich it with AI.
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_386.rpm
|
||||
sudo rpm -ivh k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
|
||||
<img src="images/image.png" width=650px; />
|
||||
**64 bit:**
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh -i k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DEB-based installation (Ubuntu/Debian)</summary>
|
||||
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
**64 bit:**
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>APK-based installation (Alpine)</summary>
|
||||
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_386.apk
|
||||
apk add k8sgpt_386.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
**64 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.2.1/k8sgpt_amd64.apk
|
||||
apk add k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->x
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Failing Installation on WSL or Linux (missing gcc)</summary>
|
||||
When installing Homebrew on WSL or Linux, you may encounter the following error:
|
||||
|
||||
```
|
||||
==> Installing k8sgpt from k8sgpt-ai/k8sgpt Error: The following formula cannot be installed from a bottle and must be
|
||||
built from the source. k8sgpt Install Clang or run brew install gcc.
|
||||
```
|
||||
|
||||
If you install gcc as suggested, the problem will persist. Therefore, you need to install the build-essential package.
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install build-essential
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## Windows
|
||||
|
||||
* Download the latest Windows binaries of **k8sgpt** from the [Release](https://github.com/k8sgpt-ai/k8sgpt/releases)
|
||||
tab based on your system architecture.
|
||||
* Extract the downloaded package to your desired location. Configure the system *path* variable with the binary location
|
||||
|
||||
|
||||
## Verify installation
|
||||
|
||||
* Run `k8sgpt version`
|
||||
|
||||
<hr>
|
||||
|
||||
## Quick Start
|
||||
|
||||
* Currently the default AI provider is OpenAI, you will need to generate an API key from [OpenAI](https://openai.com)
|
||||
* You can do this by running `k8sgpt generate` to open a browser link to generate it
|
||||
* Run `k8sgpt auth` to set it in k8sgpt.
|
||||
* You can provide the password directly using the `--password` flag.
|
||||
* Run `k8sgpt filters` to manage the active filters used by the analyzer. By default, all filters are executed during analysis.
|
||||
* Run `k8sgpt analyze` to run a scan.
|
||||
* And use `k8sgpt analyze --explain` to get a more detailed explanation of the issues.
|
||||
|
||||
<img src="images/demo4.gif" width=650px; />
|
||||
|
||||
## Analyzers
|
||||
|
||||
K8sGPT uses analyzers to triage and diagnose issues in your cluster. It has a set of analyzers that are built in, but
|
||||
you will be able to write your own analyzers.
|
||||
|
||||
### Built in analyzers
|
||||
|
||||
#### Enabled by default
|
||||
|
||||
- [x] podAnalyzer
|
||||
- [x] pvcAnalyzer
|
||||
- [x] rsAnalyzer
|
||||
- [x] serviceAnalyzer
|
||||
- [x] eventAnalyzer
|
||||
- [x] ingressAnalyzer
|
||||
- [x] statefulSetAnalyzer
|
||||
|
||||
#### Optional
|
||||
|
||||
- [x] hpaAnalyzer
|
||||
- [x] pdbAnalyzer
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
# Ensure KUBECONFIG env is set to an active Kubernetes cluster
|
||||
k8sgpt auth key <Your OpenAI key>
|
||||
Usage:
|
||||
k8sgpt [command]
|
||||
|
||||
k8sgpt find problems
|
||||
# for more detail
|
||||
k8s find problems --explain
|
||||
Available Commands:
|
||||
analyze This command will find problems within your Kubernetes cluster
|
||||
auth Authenticate with your chosen backend
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
filters Manage filters for analyzing Kubernetes resources
|
||||
generate Generate Key for your chosen backend (opens browser)
|
||||
help Help about any command
|
||||
version Print the version number of k8sgpt
|
||||
|
||||
Flags:
|
||||
--config string config file (default is $HOME/.k8sgpt.git.yaml)
|
||||
-h, --help help for k8sgpt
|
||||
--kubeconfig string Path to a kubeconfig. Only required if out-of-cluster.
|
||||
--master string The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.
|
||||
-t, --toggle Help message for toggle
|
||||
|
||||
Use "k8sgpt [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
### What about kubectl-ai?
|
||||
_Manage filters_
|
||||
|
||||
The the kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the cluster. It is not what we are trying to do here, it is focusing on writing YAML manifests.
|
||||
_List filters_
|
||||
|
||||
K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find the root cause of an issue.
|
||||
```
|
||||
k8sgpt filters list
|
||||
```
|
||||
|
||||
_Add default filters_
|
||||
|
||||
```
|
||||
k8sgpt filters add [filter(s)]
|
||||
```
|
||||
|
||||
### Examples :
|
||||
|
||||
- Simple filter : `k8sgpt filters add Service`
|
||||
- Multiple filters : `k8sgpt filters add Ingress,Pod`
|
||||
|
||||
_Add default filters_
|
||||
|
||||
```
|
||||
k8sgpt filters remove [filter(s)]
|
||||
```
|
||||
|
||||
### Examples :
|
||||
|
||||
- Simple filter : `k8sgpt filters remove Service`
|
||||
- Multiple filters : `k8sgpt filters remove Ingress,Pod`
|
||||
|
||||
_Run a scan with the default analyzers_
|
||||
|
||||
```
|
||||
k8sgpt generate
|
||||
k8sgpt auth
|
||||
k8sgpt analyze --explain
|
||||
```
|
||||
|
||||
_Filter on resource_
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --filter=Service
|
||||
```
|
||||
|
||||
_Filter by namespace_
|
||||
```
|
||||
k8sgpt analyze --explain --filter=Pod --namespace=default
|
||||
```
|
||||
|
||||
_Output to JSON_
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --filter=Service --output=json
|
||||
```
|
||||
|
||||
_Anonymize during explain_
|
||||
|
||||
```
|
||||
k8sgpt analyze --explain --filter=Service --output=json --anonymize
|
||||
```
|
||||
|
||||
### How does anonymization work?
|
||||
|
||||
With this option, the data is anonymized before being sent to the AI Backend. During the analysis execution, `k8sgpt` retrieves sensitive data (Kubernetes object names, labels, etc.). This data is masked when sent to the AI backend and replaced by a key that can be used to de-anonymize the data when the solution is returned to the user.
|
||||
|
||||
<details>
|
||||
|
||||
1. Error reported during analysis:
|
||||
```bash
|
||||
Error: HorizontalPodAutoscaler uses StatefulSet/fake-deployment as ScaleTargetRef which does not exist.
|
||||
```
|
||||
|
||||
2. Payload sent to the AI backend:
|
||||
```bash
|
||||
Error: HorizontalPodAutoscaler uses StatefulSet/tGLcCRcHa1Ce5Rs as ScaleTargetRef which does not exist.
|
||||
```
|
||||
|
||||
3. Payload returned by the AI:
|
||||
```bash
|
||||
The Kubernetes system is trying to scale a StatefulSet named tGLcCRcHa1Ce5Rs using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.
|
||||
```
|
||||
|
||||
4. Payload returned to the user:
|
||||
```bash
|
||||
The Kubernetes system is trying to scale a StatefulSet named fake-deployment using the HorizontalPodAutoscaler, but it cannot find the StatefulSet. The solution is to verify that the StatefulSet name is spelled correctly and exists in the same namespace as the HorizontalPodAutoscaler.
|
||||
```
|
||||
|
||||
**Anonymization does not currently apply to events.**
|
||||
|
||||
## Upcoming major milestones
|
||||
|
||||
- [ ] Multiple AI backend support
|
||||
- [ ] Custom AI/ML model backend support
|
||||
- [ ] Custom analyzers
|
||||
|
||||
## What about kubectl-ai?
|
||||
|
||||
The kubectl-ai [project](https://github.com/sozercan/kubectl-ai) uses AI to create manifests and apply them to the
|
||||
cluster. It is not what we are trying to do here, it is focusing on writing YAML manifests.
|
||||
|
||||
K8sgpt is focused on triaging and diagnosing issues in your cluster. It is a tool for SRE, Platform & DevOps engineers
|
||||
to help them understand what is going on in their cluster. Cutting through the noise of logs and multiple tools to find
|
||||
the root cause of an issue.
|
||||
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
`k8sgpt` stores config data in `~/.k8sgpt` the data is stored in plain text, including your OpenAI key.
|
||||
`k8sgpt` stores config data in `~/.k8sgpt.yaml` the data is stored in plain text, including your OpenAI key.
|
||||
|
||||
### Contributing
|
||||
## Contributing
|
||||
|
||||
Please read our [contributing guide](./CONTRIBUTING.md).
|
||||
### Community
|
||||
* Find us on [Slack](https://cloud-native.slack.com/channels/k8sgpt-ai)
|
||||
## Community
|
||||
Find us on [Slack](https://k8sgpt.slack.com/)
|
||||
|
||||
<a href="https://github.com/k8sgpt-ai/k8sgpt/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=k8sgpt-ai/k8sgpt" />
|
||||
</a>
|
||||
|
||||
11
SECURITY.md
Normal file
11
SECURITY.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We currently support the latest release for security patching and will deploy forward releases.
|
||||
|
||||
For example if there is a vulnerability in release `0.1.0` we will fix that release in version `0.1.1-fix` or `0.1.1`
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you are aware of a vulnverability please feel free to disclose it responsibly to contact@k8sgpt.ai or to one of our maintainers in our Slack community.
|
||||
137
cmd/analyze/analyze.go
Normal file
137
cmd/analyze/analyze.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package analyze
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analysis"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
explain bool
|
||||
backend string
|
||||
output string
|
||||
filters []string
|
||||
language string
|
||||
nocache bool
|
||||
namespace string
|
||||
anonymize bool
|
||||
)
|
||||
|
||||
// AnalyzeCmd represents the problems command
|
||||
var AnalyzeCmd = &cobra.Command{
|
||||
Use: "analyze",
|
||||
Aliases: []string{"analyse"},
|
||||
Short: "This command will find problems within your Kubernetes cluster",
|
||||
Long: `This command will find problems within your Kubernetes cluster and
|
||||
provide you with a list of issues that need to be resolved`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// get ai configuration
|
||||
var configAI ai.AIConfiguration
|
||||
err := viper.UnmarshalKey("ai", &configAI)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(configAI.Providers) == 0 {
|
||||
color.Red("Error: AI provider not specified in configuration. Please run k8sgpt auth")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var aiProvider ai.AIProvider
|
||||
for _, provider := range configAI.Providers {
|
||||
if backend == provider.Name {
|
||||
aiProvider = provider
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if aiProvider.Name == "" {
|
||||
color.Red("Error: AI provider %s not specified in configuration. Please run k8sgpt auth", backend)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
aiClient := ai.NewClient(aiProvider.Name)
|
||||
if err := aiClient.Configure(aiProvider.Password, aiProvider.Model, language); err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// Get kubernetes client from viper
|
||||
|
||||
kubecontext := viper.GetString("kubecontext")
|
||||
kubeconfig := viper.GetString("kubeconfig")
|
||||
client, err := kubernetes.NewClient(kubecontext, kubeconfig)
|
||||
if err != nil {
|
||||
color.Red("Error initialising kubernetes client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// AnalysisResult configuration
|
||||
config := &analysis.Analysis{
|
||||
Namespace: namespace,
|
||||
NoCache: nocache,
|
||||
Filters: filters,
|
||||
Explain: explain,
|
||||
AIClient: aiClient,
|
||||
Client: client,
|
||||
Context: ctx,
|
||||
}
|
||||
|
||||
err = config.RunAnalysis()
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if explain {
|
||||
err := config.GetAIResults(output, anonymize)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// print results
|
||||
switch output {
|
||||
case "json":
|
||||
output, err := config.JsonOutput()
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
default:
|
||||
config.PrintOutput()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
// namespace flag
|
||||
AnalyzeCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to analyze")
|
||||
// no cache flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&nocache, "no-cache", "c", false, "Do not use cached data")
|
||||
// anonymize flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&anonymize, "anonymize", "a", false, "Anonymize data before sending it to the AI backend. This flag masks sensitive data, such as Kubernetes object names and labels, by replacing it with a key. However, please note that this flag does not currently apply to events.")
|
||||
// array of strings flag
|
||||
AnalyzeCmd.Flags().StringSliceVarP(&filters, "filter", "f", []string{}, "Filter for these analyzers (e.g. Pod, PersistentVolumeClaim, Service, ReplicaSet)")
|
||||
// explain flag
|
||||
AnalyzeCmd.Flags().BoolVarP(&explain, "explain", "e", false, "Explain the problem to me")
|
||||
// add flag for backend
|
||||
AnalyzeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
|
||||
// output as json
|
||||
AnalyzeCmd.Flags().StringVarP(&output, "output", "o", "text", "Output format (text, json)")
|
||||
// add language options for output
|
||||
AnalyzeCmd.Flags().StringVarP(&language, "language", "l", "english", "Languages to use for AI (e.g. 'English', 'Spanish', 'French', 'German', 'Italian', 'Portuguese', 'Dutch', 'Russian', 'Chinese', 'Japanese', 'Korean')")
|
||||
}
|
||||
@@ -1,24 +1,103 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
backend string
|
||||
password string
|
||||
model string
|
||||
)
|
||||
|
||||
// authCmd represents the auth command
|
||||
var AuthCmd = &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "A brief description of your command",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Short: "Authenticate with your chosen backend",
|
||||
Long: `Provide the necessary credentials to authenticate with your chosen backend.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
|
||||
// get ai configuration
|
||||
var configAI ai.AIConfiguration
|
||||
err := viper.UnmarshalKey("ai", &configAI)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// search for provider with same name
|
||||
providerIndex := -1
|
||||
for i, provider := range configAI.Providers {
|
||||
if backend == provider.Name {
|
||||
providerIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check if backend is not empty
|
||||
if backend == "" {
|
||||
color.Red("Error: Backend AI cannot be empty.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
color.Green("Using %s as backend AI provider", backend)
|
||||
|
||||
// check if model is not empty
|
||||
if model == "" {
|
||||
color.Red("Error: Model cannot be empty.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
fmt.Printf("Enter %s Key: ", backend)
|
||||
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
color.Red("Error reading %s Key from stdin: %s", backend,
|
||||
err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
password = strings.TrimSpace(string(bytePassword))
|
||||
}
|
||||
|
||||
// create new provider object
|
||||
newProvider := ai.AIProvider{
|
||||
Name: backend,
|
||||
Model: model,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if providerIndex == -1 {
|
||||
// provider with same name does not exist, add new provider to list
|
||||
configAI.Providers = append(configAI.Providers, newProvider)
|
||||
color.Green("New provider added")
|
||||
} else {
|
||||
// provider with same name exists, update provider info
|
||||
configAI.Providers[providerIndex] = newProvider
|
||||
color.Green("Provider updated")
|
||||
}
|
||||
viper.Set("ai", configAI)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
color.Green("key added")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
// add flag for backend
|
||||
AuthCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
|
||||
// add flag for model
|
||||
AuthCmd.Flags().StringVarP(&model, "model", "m", "gpt-3.5-turbo", "Backend AI model")
|
||||
// add flag for password
|
||||
AuthCmd.Flags().StringVarP(&password, "password", "p", "", "Backend AI password")
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// keyCmd represents the key command
|
||||
var keyCmd = &cobra.Command{
|
||||
Use: "key",
|
||||
Short: "Add a key to OpenAI",
|
||||
Long: `This command will add a key from OpenAI to enable you to interact with the API`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
fmt.Print("Enter OpenAI API Key: ")
|
||||
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
color.Red("Error reading OpenAI API Key from stdin: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
password := strings.TrimSpace(string(bytePassword))
|
||||
|
||||
viper.Set("openai_api_key", password)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
color.Green("key added")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
AuthCmd.AddCommand(keyCmd)
|
||||
|
||||
}
|
||||
72
cmd/filters/add.go
Normal file
72
cmd/filters/add.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var addCmd = &cobra.Command{
|
||||
Use: "add [filter(s)]",
|
||||
Short: "Adds one or more new filters.",
|
||||
Long: `The add command adds one or more new filters to the default set of filters used by the analyze.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
inputFilters := strings.Split(args[0], ",")
|
||||
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
|
||||
|
||||
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
|
||||
// Verify filter exist
|
||||
invalidFilters := []string{}
|
||||
for _, f := range inputFilters {
|
||||
if f == "" {
|
||||
color.Red("Filter cannot be empty. Please use correct syntax.")
|
||||
os.Exit(1)
|
||||
}
|
||||
foundFilter := false
|
||||
for _, filter := range availableFilters {
|
||||
if filter == f {
|
||||
foundFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundFilter {
|
||||
invalidFilters = append(invalidFilters, f)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidFilters) != 0 {
|
||||
color.Red("Filter %s does not exist. Please use k8sgpt filters list", strings.Join(invalidFilters, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Get defined active_filters
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
if len(activeFilters) == 0 {
|
||||
activeFilters = coreFilters
|
||||
}
|
||||
|
||||
mergedFilters := append(activeFilters, inputFilters...)
|
||||
|
||||
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(mergedFilters)
|
||||
|
||||
// Verify dupplicate
|
||||
if len(dupplicatedFilters) != 0 {
|
||||
color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
viper.Set("active_filters", uniqueFilters)
|
||||
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
color.Green("Filter %s added", strings.Join(inputFilters, ", "))
|
||||
},
|
||||
}
|
||||
25
cmd/filters/filters.go
Normal file
25
cmd/filters/filters.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var FiltersCmd = &cobra.Command{
|
||||
Use: "filters",
|
||||
Aliases: []string{"filter"},
|
||||
Short: "Manage filters for analyzing Kubernetes resources",
|
||||
Long: `The filters command allows you to manage filters that are used to analyze Kubernetes resources.
|
||||
You can list available filters to analyze resources.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.Help()
|
||||
return
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
FiltersCmd.AddCommand(listCmd)
|
||||
FiltersCmd.AddCommand(addCmd)
|
||||
FiltersCmd.AddCommand(removeCmd)
|
||||
}
|
||||
52
cmd/filters/list.go
Normal file
52
cmd/filters/list.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List available filters",
|
||||
Long: `The list command displays a list of available filters that can be used to analyze Kubernetes resources.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
coreFilters, additionalFilters, integrationFilters := analyzer.ListFilters()
|
||||
|
||||
availableFilters := append(append(coreFilters, additionalFilters...), integrationFilters...)
|
||||
|
||||
if len(activeFilters) == 0 {
|
||||
activeFilters = coreFilters
|
||||
}
|
||||
inactiveFilters := util.SliceDiff(availableFilters, activeFilters)
|
||||
fmt.Printf(color.YellowString("Active: \n"))
|
||||
for _, filter := range activeFilters {
|
||||
|
||||
// if the filter is an integration, mark this differently
|
||||
if util.SliceContainsString(integrationFilters, filter) {
|
||||
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
|
||||
} else {
|
||||
fmt.Printf("> %s\n", color.GreenString(filter))
|
||||
}
|
||||
}
|
||||
|
||||
// display inactive filters
|
||||
if len(inactiveFilters) != 0 {
|
||||
fmt.Printf(color.YellowString("Unused: \n"))
|
||||
for _, filter := range inactiveFilters {
|
||||
// if the filter is an integration, mark this differently
|
||||
if util.SliceContainsString(integrationFilters, filter) {
|
||||
fmt.Printf("> %s\n", color.BlueString("%s (integration)", filter))
|
||||
} else {
|
||||
fmt.Printf("> %s\n", color.RedString(filter))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
74
cmd/filters/remove.go
Normal file
74
cmd/filters/remove.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var removeCmd = &cobra.Command{
|
||||
Use: "remove [filter(s)]",
|
||||
Short: "Remove one or more filters.",
|
||||
Long: `The add command remove one or more filters to the default set of filters used by the analyze.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
inputFilters := strings.Split(args[0], ",")
|
||||
|
||||
// Get defined active_filters
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
coreFilters, _, _ := analyzer.ListFilters()
|
||||
|
||||
if len(activeFilters) == 0 {
|
||||
activeFilters = coreFilters
|
||||
}
|
||||
|
||||
// Check if input input filters is not empty
|
||||
for _, f := range inputFilters {
|
||||
if f == "" {
|
||||
color.Red("Filter cannot be empty. Please use correct syntax.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// verify dupplicate filters example: k8sgpt filters remove Pod Pod
|
||||
uniqueFilters, dupplicatedFilters := util.RemoveDuplicates(inputFilters)
|
||||
if len(dupplicatedFilters) != 0 {
|
||||
color.Red("Duplicate filters found: %s", strings.Join(dupplicatedFilters, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Verify if filter exist in config file and update default_filter
|
||||
filterNotFound := []string{}
|
||||
for _, filter := range uniqueFilters {
|
||||
foundFilter := false
|
||||
for i, f := range activeFilters {
|
||||
if f == filter {
|
||||
foundFilter = true
|
||||
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundFilter {
|
||||
filterNotFound = append(filterNotFound, filter)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filterNotFound) != 0 {
|
||||
color.Red("Filter(s) %s does not exist in configuration file. Please use k8sgpt filters add.", strings.Join(filterNotFound, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
viper.Set("active_filters", activeFilters)
|
||||
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
color.Green("Filter(s) %s removed", strings.Join(inputFilters, ", "))
|
||||
},
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package find
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// findCmd represents the find command
|
||||
var FindCmd = &cobra.Command{
|
||||
Use: "find",
|
||||
Short: "Find issues within your Kubernetes cluster",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package find
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var explain bool
|
||||
|
||||
// problemsCmd represents the problems command
|
||||
var problemsCmd = &cobra.Command{
|
||||
Use: "problems",
|
||||
Short: "This command will find problems within your Kubernetes cluster",
|
||||
Long: `This command will find problems within your Kubernetes cluster and
|
||||
provide you with a list of issues that need to be resolved`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Initialise the openAI client
|
||||
openAIClient, err := ai.NewClient()
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// Get kubernetes client from viper
|
||||
client := viper.Get("kubernetesClient").(*kubernetes.Client)
|
||||
|
||||
if err := analyzer.RunAnalysis(ctx, client, openAIClient, explain); err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
problemsCmd.Flags().BoolVarP(&explain, "explain", "e", false, "Explain the problem to me")
|
||||
|
||||
FindCmd.AddCommand(problemsCmd)
|
||||
|
||||
}
|
||||
77
cmd/generate/generate.go
Normal file
77
cmd/generate/generate.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
backend string
|
||||
)
|
||||
|
||||
// generateCmd represents the auth command
|
||||
var GenerateCmd = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate Key for your chosen backend (opens browser)",
|
||||
Long: `Opens your browser to generate a key for your chosen backend.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
backendType := viper.GetString("backend_type")
|
||||
if backendType == "" {
|
||||
// Set the default backend
|
||||
backend = "openai"
|
||||
}
|
||||
// override the default backend if a flag is provided
|
||||
if backend != "" {
|
||||
backendType = backend
|
||||
}
|
||||
fmt.Println("")
|
||||
openbrowser("https://beta.openai.com/account/api-keys")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// add flag for backend
|
||||
GenerateCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
|
||||
}
|
||||
|
||||
func openbrowser(url string) {
|
||||
var err error
|
||||
isGui := true
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
_, err = exec.LookPath("xdg-open")
|
||||
if err != nil {
|
||||
isGui = false
|
||||
} else {
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
}
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
printInstructions(isGui, backend)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func printInstructions(isGui bool, backendType string) {
|
||||
fmt.Println("")
|
||||
if isGui {
|
||||
color.Green("Opening: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
|
||||
fmt.Println("")
|
||||
} else {
|
||||
color.Green("Please open: https://beta.openai.com/account/api-keys to generate a key for %s", backendType)
|
||||
fmt.Println("")
|
||||
}
|
||||
color.Green("Please copy the generated key and run `k8sgpt auth` to add it to your config file")
|
||||
fmt.Println("")
|
||||
}
|
||||
33
cmd/integration/activate.go
Normal file
33
cmd/integration/activate.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// activateCmd represents the activate command
|
||||
var activateCmd = &cobra.Command{
|
||||
Use: "activate [integration]",
|
||||
Short: "Activate an integration",
|
||||
Long: ``,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
integrationName := args[0]
|
||||
|
||||
integration := integration.NewIntegration()
|
||||
// Check if the integation exists
|
||||
err := integration.Activate(integrationName, namespace)
|
||||
if err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("Activated integration %s", integrationName)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrationCmd.AddCommand(activateCmd)
|
||||
|
||||
}
|
||||
35
cmd/integration/deactivate.go
Normal file
35
cmd/integration/deactivate.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package integration
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// deactivateCmd represents the deactivate command
|
||||
var deactivateCmd = &cobra.Command{
|
||||
Use: "deactivate [integration]",
|
||||
Short: "Deactivate an integration",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Long: `For example e.g. k8sgpt integration deactivate trivy`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
integrationName := args[0]
|
||||
|
||||
integration := integration.NewIntegration()
|
||||
|
||||
if err := integration.Deactivate(integrationName, namespace); err != nil {
|
||||
color.Red("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("Deactivated integration %s", integrationName)
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrationCmd.AddCommand(deactivateCmd)
|
||||
}
|
||||
28
cmd/integration/integration.go
Normal file
28
cmd/integration/integration.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
namespace string
|
||||
)
|
||||
|
||||
// IntegrationCmd represents the integrate command
|
||||
var IntegrationCmd = &cobra.Command{
|
||||
Use: "integration",
|
||||
Aliases: []string{"integrations"},
|
||||
Short: "Intergrate another tool into K8sGPT",
|
||||
Long: `Intergrate another tool into K8sGPT. For example:
|
||||
|
||||
k8sgpt integration activate trivy
|
||||
|
||||
This would allow you to deploy trivy into your cluster and use a K8sGPT analyzer to parse trivy results.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrationCmd.PersistentFlags().StringVarP(&namespace, "namespace", "n", "default", "The namespace to use for the integration")
|
||||
}
|
||||
50
cmd/integration/list.go
Normal file
50
cmd/integration/list.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// listCmd represents the list command
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Lists built-in integrations",
|
||||
Long: ``,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
integrationProvider := integration.NewIntegration()
|
||||
integrations := integrationProvider.List()
|
||||
|
||||
fmt.Println(color.YellowString("Active:"))
|
||||
for _, i := range integrations {
|
||||
b, err := integrationProvider.IsActivate(i)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if b {
|
||||
fmt.Printf("> %s\n", color.GreenString(i))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(color.YellowString("Unused: "))
|
||||
for _, i := range integrations {
|
||||
b, err := integrationProvider.IsActivate(i)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !b {
|
||||
fmt.Printf("> %s\n", color.GreenString(i))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrationCmd.AddCommand(listCmd)
|
||||
|
||||
}
|
||||
52
cmd/root.go
52
cmd/root.go
@@ -2,19 +2,23 @@ package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/analyze"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/auth"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/find"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/filters"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/generate"
|
||||
"github.com/k8sgpt-ai/k8sgpt/cmd/integration"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
masterURL string
|
||||
kubeconfig string
|
||||
cfgFile string
|
||||
kubecontext string
|
||||
kubeconfig string
|
||||
version string
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
@@ -29,7 +33,8 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
func Execute(v string) {
|
||||
version = v
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
@@ -39,26 +44,18 @@ func Execute() {
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
rootCmd.AddCommand(auth.AuthCmd)
|
||||
rootCmd.AddCommand(find.FindCmd)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.git.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
|
||||
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
|
||||
//Initialise the kubeconfig
|
||||
kubernetesClient, err := kubernetes.NewClient(masterURL, kubeconfig)
|
||||
if err != nil {
|
||||
color.Red("Error initialising kubernetes client: %v", err)
|
||||
var kubeconfigPath string
|
||||
if home := homedir.HomeDir(); home != "" {
|
||||
kubeconfigPath = filepath.Join(home, ".kube", "config")
|
||||
}
|
||||
|
||||
viper.Set("kubernetesClient", kubernetesClient)
|
||||
rootCmd.AddCommand(auth.AuthCmd)
|
||||
rootCmd.AddCommand(analyze.AnalyzeCmd)
|
||||
rootCmd.AddCommand(filters.FiltersCmd)
|
||||
rootCmd.AddCommand(generate.GenerateCmd)
|
||||
rootCmd.AddCommand(integration.IntegrationCmd)
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.k8sgpt.yaml)")
|
||||
rootCmd.PersistentFlags().StringVar(&kubecontext, "kubecontext", "", "Kubernetes context to use. Only required if out-of-cluster.")
|
||||
rootCmd.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", kubeconfigPath, "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
@@ -79,6 +76,9 @@ func initConfig() {
|
||||
viper.SafeWriteConfig()
|
||||
}
|
||||
|
||||
viper.Set("kubecontext", kubecontext)
|
||||
viper.Set("kubeconfig", kubeconfig)
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
|
||||
19
cmd/version.go
Normal file
19
cmd/version.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of k8sgpt",
|
||||
Long: `All software has versions. This is k8sgpt's`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Printf("k8sgpt version %s", version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.20.2-alpine3.16 AS builder
|
||||
FROM golang:1.20.3-alpine3.16 AS builder
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
|
||||
166
go.mod
166
go.mod
@@ -3,64 +3,172 @@ module github.com/k8sgpt-ai/k8sgpt
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/briandowns/spinner v1.23.0
|
||||
github.com/aquasecurity/trivy-operator v0.13.0
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/sashabaranov/go-openai v1.5.7
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mittwald/go-helm-client v0.12.1
|
||||
github.com/sashabaranov/go-openai v1.7.0
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
golang.org/x/term v0.6.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/term v0.7.0
|
||||
helm.sh/helm/v3 v3.11.2
|
||||
k8s.io/api v0.26.3
|
||||
k8s.io/apimachinery v0.26.3
|
||||
k8s.io/client-go v0.26.3
|
||||
k8s.io/kubectl v0.26.3
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.3 // indirect
|
||||
github.com/aquasecurity/defsec v0.85.0 // indirect
|
||||
github.com/aquasecurity/go-dep-parser v0.0.0-20230324043952-2172dc218241 // indirect
|
||||
github.com/aquasecurity/table v1.8.0 // indirect
|
||||
github.com/aquasecurity/tml v0.6.1 // indirect
|
||||
github.com/aquasecurity/trivy v0.39.0 // indirect
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20230116084806-4bcdf1c414d0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/containerd/containerd v1.6.19 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v23.0.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/gnostic v0.6.9 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/go-containerregistry v0.14.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lib/pq v1.10.7 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/masahiro331/go-xfs-filesystem v0.0.0-20221225060805-c02764233454 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/locker v1.0.1 // indirect
|
||||
github.com/moby/spdystream v0.2.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_golang v1.14.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rubenv/sql-migrate v1.3.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spdx/tools-golang v0.5.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
go.starlark.net v0.0.0-20221020143700-22309ac47eac // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221109205753-fc8884afc316 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/oauth2 v0.6.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2 // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.26.3 // indirect
|
||||
k8s.io/apiserver v0.26.3 // indirect
|
||||
k8s.io/cli-runtime v0.26.3 // indirect
|
||||
k8s.io/component-base v0.26.3 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c // indirect
|
||||
k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 // indirect
|
||||
oras.land/oras-go v1.2.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/kustomize/api v0.12.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
|
||||
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
|
||||
replace oras.land/oras-go => oras.land/oras-go v1.1.1
|
||||
|
||||
// v0.3.1-0.20230104082527-d6f58551be3f is taken from github.com/moby/buildkit v0.11.0
|
||||
// spdx logic write on v0.3.0 and incompatible with v0.3.1-0.20230104082527-d6f58551be3f
|
||||
replace github.com/spdx/tools-golang => github.com/spdx/tools-golang v0.3.0
|
||||
|
||||
BIN
images/demo3.png
Normal file
BIN
images/demo3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 352 KiB |
BIN
images/demo4.gif
Normal file
BIN
images/demo4.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
BIN
images/landing.png
Normal file
BIN
images/landing.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 403 KiB |
4
main.go
4
main.go
@@ -5,6 +5,8 @@ package main
|
||||
|
||||
import "github.com/k8sgpt-ai/k8sgpt/cmd"
|
||||
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
cmd.Execute(version)
|
||||
}
|
||||
|
||||
49
pkg/ai/ai.go
49
pkg/ai/ai.go
@@ -1,49 +0,0 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *openai.Client
|
||||
}
|
||||
|
||||
func (c *Client) GetClient() *openai.Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func NewClient() (*Client, error) {
|
||||
|
||||
// get the token with viper
|
||||
token := viper.GetString("openai_api_key")
|
||||
// check if nil
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("no OpenAI API Key found")
|
||||
}
|
||||
|
||||
client := openai.NewClient(token)
|
||||
return &Client{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
|
||||
Model: openai.GPT3Dot5Turbo,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: "Simplify the following Kubernetes error message and provide a solution: " + prompt,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
33
pkg/ai/iai.go
Normal file
33
pkg/ai/iai.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type IAI interface {
|
||||
Configure(token string, model string, language string) error
|
||||
GetCompletion(ctx context.Context, prompt string) (string, error)
|
||||
Parse(ctx context.Context, prompt []string, nocache bool) (string, error)
|
||||
GetName() string
|
||||
}
|
||||
|
||||
func NewClient(provider string) IAI {
|
||||
switch provider {
|
||||
case "openai":
|
||||
return &OpenAIClient{}
|
||||
case "noopai":
|
||||
return &NoOpAIClient{}
|
||||
default:
|
||||
return &OpenAIClient{}
|
||||
}
|
||||
}
|
||||
|
||||
type AIConfiguration struct {
|
||||
Providers []AIProvider `mapstructure:"providers"`
|
||||
}
|
||||
|
||||
type AIProvider struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Model string `mapstructure:"model"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
55
pkg/ai/noopai.go
Normal file
55
pkg/ai/noopai.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/viper"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NoOpAIClient struct {
|
||||
client string
|
||||
language string
|
||||
model string
|
||||
}
|
||||
|
||||
func (c *NoOpAIClient) Configure(token string, model string, language string) error {
|
||||
c.language = language
|
||||
c.client = fmt.Sprintf("I am a noop client with the token %s ", token)
|
||||
c.model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NoOpAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
response := "I am a noop response to the prompt " + prompt
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *NoOpAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) {
|
||||
// parse the text with the AI backend
|
||||
inputKey := strings.Join(prompt, " ")
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
|
||||
|
||||
response, err := a.GetCompletion(ctx, inputKey)
|
||||
if err != nil {
|
||||
color.Red("error getting completion: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !viper.IsSet(sEnc) {
|
||||
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("error writing config: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *NoOpAIClient) GetName() string {
|
||||
return "noopai"
|
||||
}
|
||||
94
pkg/ai/openai.go
Normal file
94
pkg/ai/openai.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
const (
|
||||
default_prompt = "Simplify the following Kubernetes error message and provide a solution in %s: %s"
|
||||
prompt_a = "Read the following input %s and provide possible scenarios for remediation in %s"
|
||||
prompt_b = "Considering the following input from the Kubernetes resource %s and the error message %s, provide possible scenarios for remediation in %s"
|
||||
prompt_c = "Reading the following %s error message and it's accompanying log message %s, how would you simplify this message?"
|
||||
)
|
||||
|
||||
type OpenAIClient struct {
|
||||
client *openai.Client
|
||||
language string
|
||||
model string
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) Configure(token string, model string, language string) error {
|
||||
client := openai.NewClient(token)
|
||||
if client == nil {
|
||||
return errors.New("error creating OpenAI client")
|
||||
}
|
||||
c.language = language
|
||||
c.client = client
|
||||
c.model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
// Create a completion request
|
||||
resp, err := c.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
|
||||
Model: c.model,
|
||||
Messages: []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: "user",
|
||||
Content: fmt.Sprintf(default_prompt, c.language, prompt),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
func (a *OpenAIClient) Parse(ctx context.Context, prompt []string, nocache bool) (string, error) {
|
||||
inputKey := strings.Join(prompt, " ")
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputKey))
|
||||
// find in viper cache
|
||||
if viper.IsSet(sEnc) && !nocache {
|
||||
// retrieve data from cache
|
||||
response := viper.GetString(sEnc)
|
||||
if response == "" {
|
||||
color.Red("error retrieving cached data")
|
||||
return "", nil
|
||||
}
|
||||
output, err := base64.StdEncoding.DecodeString(response)
|
||||
if err != nil {
|
||||
color.Red("error decoding cached data: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
response, err := a.GetCompletion(ctx, inputKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !viper.IsSet(sEnc) {
|
||||
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("error writing config: %v", err)
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (a *OpenAIClient) GetName() string {
|
||||
return "openai"
|
||||
}
|
||||
189
pkg/analysis/analysis.go
Normal file
189
pkg/analysis/analysis.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/analyzer"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Analysis struct {
|
||||
Context context.Context
|
||||
Filters []string
|
||||
Client *kubernetes.Client
|
||||
AIClient ai.IAI
|
||||
Results []common.Result
|
||||
Namespace string
|
||||
NoCache bool
|
||||
Explain bool
|
||||
}
|
||||
|
||||
type AnalysisStatus string
|
||||
|
||||
const (
|
||||
StateOK AnalysisStatus = "OK"
|
||||
StateProblemDetected AnalysisStatus = "ProblemDetected"
|
||||
)
|
||||
|
||||
type JsonOutput struct {
|
||||
Status AnalysisStatus `json:"status"`
|
||||
Problems int `json:"problems"`
|
||||
Results []common.Result `json:"results"`
|
||||
}
|
||||
|
||||
func (a *Analysis) RunAnalysis() error {
|
||||
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
analyzerMap := analyzer.GetAnalyzerMap()
|
||||
|
||||
analyzerConfig := common.Analyzer{
|
||||
Client: a.Client,
|
||||
Context: a.Context,
|
||||
Namespace: a.Namespace,
|
||||
AIClient: a.AIClient,
|
||||
}
|
||||
|
||||
// if there are no filters selected and no active_filters then run all of them
|
||||
if len(a.Filters) == 0 && len(activeFilters) == 0 {
|
||||
for _, analyzer := range analyzerMap {
|
||||
results, err := analyzer.Analyze(analyzerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Results = append(a.Results, results...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if the filters flag is specified
|
||||
if len(a.Filters) != 0 {
|
||||
for _, filter := range a.Filters {
|
||||
if analyzer, ok := analyzerMap[filter]; ok {
|
||||
results, err := analyzer.Analyze(analyzerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Results = append(a.Results, results...)
|
||||
} else {
|
||||
return errors.New(fmt.Sprintf("\"%s\" filter does not exist. Please run k8sgpt filters list.", filter))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// use active_filters
|
||||
for _, filter := range activeFilters {
|
||||
if analyzer, ok := analyzerMap[filter]; ok {
|
||||
results, err := analyzer.Analyze(analyzerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Results = append(a.Results, results...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analysis) JsonOutput() ([]byte, error) {
|
||||
var problems int
|
||||
var status AnalysisStatus
|
||||
for _, result := range a.Results {
|
||||
problems += len(result.Error)
|
||||
}
|
||||
if problems > 0 {
|
||||
status = StateProblemDetected
|
||||
} else {
|
||||
status = StateOK
|
||||
}
|
||||
|
||||
result := JsonOutput{
|
||||
Problems: problems,
|
||||
Results: a.Results,
|
||||
Status: status,
|
||||
}
|
||||
output, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshalling json: %v", err)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (a *Analysis) PrintOutput() {
|
||||
fmt.Println("")
|
||||
if len(a.Results) == 0 {
|
||||
fmt.Println(color.GreenString("No problems detected"))
|
||||
}
|
||||
for n, result := range a.Results {
|
||||
fmt.Printf("%s %s(%s)\n", color.CyanString("%d", n),
|
||||
color.YellowString(result.Name), color.CyanString(result.ParentObject))
|
||||
for _, err := range result.Error {
|
||||
fmt.Printf("- %s %s\n", color.RedString("Error:"), color.RedString(err.Text))
|
||||
}
|
||||
fmt.Println(color.GreenString(result.Details + "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analysis) GetAIResults(output string, anonymize bool) error {
|
||||
if len(a.Results) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bar *progressbar.ProgressBar
|
||||
if output != "json" {
|
||||
bar = progressbar.Default(int64(len(a.Results)))
|
||||
}
|
||||
|
||||
for index, analysis := range a.Results {
|
||||
var texts []string
|
||||
|
||||
for _, failure := range analysis.Error {
|
||||
if anonymize {
|
||||
for _, s := range failure.Sensitive {
|
||||
failure.Text = util.ReplaceIfMatch(failure.Text, s.Unmasked, s.Masked)
|
||||
}
|
||||
}
|
||||
texts = append(texts, failure.Text)
|
||||
}
|
||||
parsedText, err := a.AIClient.Parse(a.Context, texts, a.NoCache)
|
||||
if err != nil {
|
||||
// FIXME: can we avoid checking if output is json multiple times?
|
||||
// maybe implement the progress bar better?
|
||||
if output != "json" {
|
||||
bar.Exit()
|
||||
}
|
||||
|
||||
// Check for exhaustion
|
||||
if strings.Contains(err.Error(), "status code: 429") {
|
||||
return fmt.Errorf("exhausted API quota for AI provider %s: %v", a.AIClient.GetName(), err)
|
||||
} else {
|
||||
return fmt.Errorf("failed while calling AI provider %s: %v", a.AIClient.GetName(), err)
|
||||
}
|
||||
}
|
||||
|
||||
if anonymize {
|
||||
for _, failure := range analysis.Error {
|
||||
for _, s := range failure.Sensitive {
|
||||
parsedText = strings.ReplaceAll(parsedText, s.Masked, s.Unmasked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analysis.Details = parsedText
|
||||
if output != "json" {
|
||||
bar.Add(1)
|
||||
}
|
||||
a.Results[index] = analysis
|
||||
}
|
||||
return nil
|
||||
}
|
||||
154
pkg/analysis/analysis_test.go
Normal file
154
pkg/analysis/analysis_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAnalysis_NoProblemJsonOutput(t *testing.T) {
|
||||
|
||||
analysis := Analysis{
|
||||
Results: []common.Result{},
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
expected := JsonOutput{
|
||||
Status: StateOK,
|
||||
Problems: 0,
|
||||
Results: []common.Result{},
|
||||
}
|
||||
|
||||
gotJson, err := analysis.JsonOutput()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
got := JsonOutput{}
|
||||
err = json.Unmarshal(gotJson, &got)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fmt.Println(got)
|
||||
fmt.Println(expected)
|
||||
|
||||
require.Equal(t, got, expected)
|
||||
}
|
||||
|
||||
func TestAnalysis_ProblemJsonOutput(t *testing.T) {
|
||||
analysis := Analysis{
|
||||
Results: []common.Result{
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []common.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
expected := JsonOutput{
|
||||
Status: StateProblemDetected,
|
||||
Problems: 1,
|
||||
Results: []common.Result{
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []common.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
}
|
||||
|
||||
gotJson, err := analysis.JsonOutput()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
got := JsonOutput{}
|
||||
err = json.Unmarshal(gotJson, &got)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fmt.Println(got)
|
||||
fmt.Println(expected)
|
||||
|
||||
require.Equal(t, got, expected)
|
||||
}
|
||||
|
||||
func TestAnalysis_MultipleProblemJsonOutput(t *testing.T) {
|
||||
analysis := Analysis{
|
||||
Results: []common.Result{
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []common.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
{
|
||||
Text: "another-test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
expected := JsonOutput{
|
||||
Status: StateProblemDetected,
|
||||
Problems: 2,
|
||||
Results: []common.Result{
|
||||
{
|
||||
Kind: "Deployment",
|
||||
Name: "test-deployment",
|
||||
Error: []common.Failure{
|
||||
{
|
||||
Text: "test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
{
|
||||
Text: "another-test-problem",
|
||||
Sensitive: []common.Sensitive{},
|
||||
},
|
||||
},
|
||||
Details: "test-solution",
|
||||
ParentObject: "parent-resource"},
|
||||
},
|
||||
}
|
||||
|
||||
gotJson, err := analysis.JsonOutput()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
got := JsonOutput{}
|
||||
err = json.Unmarshal(gotJson, &got)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fmt.Println(got)
|
||||
fmt.Println(expected)
|
||||
|
||||
require.Equal(t, got, expected)
|
||||
}
|
||||
@@ -1,20 +1,88 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration"
|
||||
)
|
||||
|
||||
func RunAnalysis(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error {
|
||||
err := AnalyzePod(ctx, client, aiClient, explain)
|
||||
if err != nil {
|
||||
return err
|
||||
var coreAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"Pod": PodAnalyzer{},
|
||||
"ReplicaSet": ReplicaSetAnalyzer{},
|
||||
"PersistentVolumeClaim": PvcAnalyzer{},
|
||||
"Service": ServiceAnalyzer{},
|
||||
"Ingress": IngressAnalyzer{},
|
||||
"StatefulSet": StatefulSetAnalyzer{},
|
||||
}
|
||||
|
||||
var additionalAnalyzerMap = map[string]common.IAnalyzer{
|
||||
"HorizontalPodAutoScaler": HpaAnalyzer{},
|
||||
"PodDisruptionBudget": PdbAnalyzer{},
|
||||
}
|
||||
|
||||
func ListFilters() ([]string, []string, []string) {
|
||||
coreKeys := make([]string, 0, len(coreAnalyzerMap))
|
||||
for k := range coreAnalyzerMap {
|
||||
coreKeys = append(coreKeys, k)
|
||||
}
|
||||
|
||||
err = AnalyzeReplicaSet(ctx, client, aiClient, explain)
|
||||
if err != nil {
|
||||
return err
|
||||
additionalKeys := make([]string, 0, len(additionalAnalyzerMap))
|
||||
for k := range additionalAnalyzerMap {
|
||||
additionalKeys = append(additionalKeys, k)
|
||||
}
|
||||
return nil
|
||||
|
||||
integrationProvider := integration.NewIntegration()
|
||||
var integrationAnalyzers []string
|
||||
|
||||
for _, i := range integrationProvider.List() {
|
||||
b, _ := integrationProvider.IsActivate(i)
|
||||
if b {
|
||||
in, err := integrationProvider.Get(i)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
integrationAnalyzers = append(integrationAnalyzers, in.GetAnalyzerName())
|
||||
}
|
||||
}
|
||||
|
||||
return coreKeys, additionalKeys, integrationAnalyzers
|
||||
}
|
||||
|
||||
func GetAnalyzerMap() map[string]common.IAnalyzer {
|
||||
|
||||
mergedMap := make(map[string]common.IAnalyzer)
|
||||
|
||||
// add core analyzer
|
||||
for key, value := range coreAnalyzerMap {
|
||||
mergedMap[key] = value
|
||||
}
|
||||
|
||||
// add additional analyzer
|
||||
for key, value := range additionalAnalyzerMap {
|
||||
mergedMap[key] = value
|
||||
}
|
||||
|
||||
integrationProvider := integration.NewIntegration()
|
||||
|
||||
for _, i := range integrationProvider.List() {
|
||||
b, err := integrationProvider.IsActivate(i)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
if b {
|
||||
in, err := integrationProvider.Get(i)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
in.AddAnalyzer(&mergedMap)
|
||||
}
|
||||
}
|
||||
|
||||
return mergedMap
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func FetchLatestPodEvent(ctx context.Context, kubernetesClient *kubernetes.Client, pod *v1.Pod) (*v1.Event, error) {
|
||||
func FetchLatestEvent(ctx context.Context, kubernetesClient *kubernetes.Client, namespace string, name string) (*v1.Event, error) {
|
||||
|
||||
// get the list of events
|
||||
events, err := kubernetesClient.GetClient().CoreV1().Events(pod.Namespace).List(ctx,
|
||||
events, err := kubernetesClient.GetClient().CoreV1().Events(namespace).List(ctx,
|
||||
metav1.ListOptions{
|
||||
FieldSelector: "involvedObject.name=" + pod.Name,
|
||||
FieldSelector: "involvedObject.name=" + name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
91
pkg/analyzer/hpa.go
Normal file
91
pkg/analyzer/hpa.go
Normal file
@@ -0,0 +1,91 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type HpaAnalyzer struct{}
|
||||
|
||||
func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, hpa := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// check ScaleTargetRef exist
|
||||
scaleTargetRef := hpa.Spec.ScaleTargetRef
|
||||
scaleTargetRefNotFound := false
|
||||
|
||||
switch scaleTargetRef.Kind {
|
||||
case "Deployment":
|
||||
_, err := a.Client.GetClient().AppsV1().Deployments(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
scaleTargetRefNotFound = true
|
||||
}
|
||||
case "ReplicationController":
|
||||
_, err := a.Client.GetClient().CoreV1().ReplicationControllers(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
scaleTargetRefNotFound = true
|
||||
}
|
||||
case "ReplicaSet":
|
||||
_, err := a.Client.GetClient().AppsV1().ReplicaSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
scaleTargetRefNotFound = true
|
||||
}
|
||||
case "StatefulSet":
|
||||
_, err := a.Client.GetClient().AppsV1().StatefulSets(hpa.Namespace).Get(a.Context, scaleTargetRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
scaleTargetRefNotFound = true
|
||||
}
|
||||
default:
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s as ScaleTargetRef which is not an option.", scaleTargetRef.Kind),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
|
||||
if scaleTargetRefNotFound {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("HorizontalPodAutoscaler uses %s/%s as ScaleTargetRef which does not exist.", scaleTargetRef.Kind, scaleTargetRef.Name),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: scaleTargetRef.Name,
|
||||
Masked: util.MaskString(scaleTargetRef.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", hpa.Namespace, hpa.Name)] = common.PreAnalysis{
|
||||
HorizontalPodAutoscalers: hpa,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "HorizontalPodAutoscaler",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.HorizontalPodAutoscalers.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
206
pkg/analyzer/hpaAnalyzer_test.go
Normal file
206
pkg/analyzer/hpaAnalyzer_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestHPAAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerWithMultipleHPA(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
)
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 2)
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerWithUnsuportedScaleTargetRef(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Kind: "unsupported",
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err.Text, "which is not an option.") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Error("expected error 'does not possible option.' not found in analysis results")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerWithNonExistentScaleTargetRef(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "non-existent",
|
||||
},
|
||||
},
|
||||
})
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err.Text, "does not exist.") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Error("expected error 'does not exist.' not found in analysis results")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHPAAnalyzerWithExistingScaleTargetRef(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: autoscalingv1.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv1.CrossVersionObjectReference{
|
||||
Kind: "Deployment",
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
)
|
||||
hpaAnalyzer := HpaAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := hpaAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for _, analysis := range analysisResults {
|
||||
assert.Equal(t, len(analysis.Error), 0)
|
||||
}
|
||||
}
|
||||
127
pkg/analyzer/ingress.go
Normal file
127
pkg/analyzer/ingress.go
Normal file
@@ -0,0 +1,127 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type IngressAnalyzer struct{}
|
||||
|
||||
func (IngressAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
list, err := a.Client.GetClient().NetworkingV1().Ingresses(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, ing := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// get ingressClassName
|
||||
ingressClassName := ing.Spec.IngressClassName
|
||||
if ingressClassName == nil {
|
||||
ingClassValue := ing.Annotations["kubernetes.io/ingress.class"]
|
||||
if ingClassValue == "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Ingress %s/%s does not specify an Ingress class.", ing.Namespace, ing.Name),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: ing.Name,
|
||||
Masked: util.MaskString(ing.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
ingressClassName = &ingClassValue
|
||||
}
|
||||
}
|
||||
|
||||
// check if ingressclass exist
|
||||
if ingressClassName != nil {
|
||||
_, err := a.Client.GetClient().NetworkingV1().IngressClasses().Get(a.Context, *ingressClassName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the ingress class %s which does not exist.", *ingressClassName),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: *ingressClassName,
|
||||
Masked: util.MaskString(*ingressClassName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// loop over rules
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
// loop over paths
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
_, err := a.Client.GetClient().CoreV1().Services(ing.Namespace).Get(a.Context, path.Backend.Service.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the service %s/%s which does not exist.", ing.Namespace, path.Backend.Service.Name),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: path.Backend.Service.Name,
|
||||
Masked: util.MaskString(path.Backend.Service.Name),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tls := range ing.Spec.TLS {
|
||||
_, err := a.Client.GetClient().CoreV1().Secrets(ing.Namespace).Get(a.Context, tls.SecretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Ingress uses the secret %s/%s as a TLS certificate which does not exist.", ing.Namespace, tls.SecretName),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: ing.Namespace,
|
||||
Masked: util.MaskString(ing.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: tls.SecretName,
|
||||
Masked: util.MaskString(tls.SecretName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", ing.Namespace, ing.Name)] = common.PreAnalysis{
|
||||
Ingress: ing,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "Ingress",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Ingress.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
115
pkg/analyzer/ingress_test.go
Normal file
115
pkg/analyzer/ingress_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestIngressAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
)
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 2)
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var errorFound bool
|
||||
for _, analysis := range analysisResults {
|
||||
for _, err := range analysis.Error {
|
||||
if strings.Contains(err.Text, "does not specify an Ingress class") {
|
||||
errorFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Error("expected error 'does not specify an Ingress class' not found in analysis results")
|
||||
}
|
||||
}
|
||||
82
pkg/analyzer/pdb.go
Normal file
82
pkg/analyzer/pdb.go
Normal file
@@ -0,0 +1,82 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type PdbAnalyzer struct{}
|
||||
|
||||
func (PdbAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
list, err := a.Client.GetClient().PolicyV1().PodDisruptionBudgets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, pdb := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pdb.Namespace, pdb.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if evt.Reason == "NoPods" && evt.Message != "" {
|
||||
if pdb.Spec.Selector != nil {
|
||||
for k, v := range pdb.Spec.Selector.MatchLabels {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s, expected label %s=%s", evt.Message, k, v),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: k,
|
||||
Masked: util.MaskString(k),
|
||||
},
|
||||
{
|
||||
Unmasked: v,
|
||||
Masked: util.MaskString(v),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
for _, v := range pdb.Spec.Selector.MatchExpressions {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s, expected expression %s", evt.Message, v),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("%s, selector is nil", evt.Message),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pdb.Namespace, pdb.Name)] = common.PreAnalysis{
|
||||
PodDisruptionBudget: pdb,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "PodDisruptionBudget",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.PodDisruptionBudget.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, err
|
||||
}
|
||||
89
pkg/analyzer/pod.go
Normal file
89
pkg/analyzer/pod.go
Normal file
@@ -0,0 +1,89 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type PodAnalyzer struct {
|
||||
}
|
||||
|
||||
func (PodAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().Pods(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, pod := range list.Items {
|
||||
var failures []common.Failure
|
||||
// Check for pending pods
|
||||
if pod.Status.Phase == "Pending" {
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, containerStatus := range pod.Status.Conditions {
|
||||
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
|
||||
if containerStatus.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: containerStatus.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil {
|
||||
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
|
||||
if containerStatus.State.Waiting.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: containerStatus.State.Waiting.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
|
||||
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pod.Namespace, pod.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "FailedCreatePodSandBox" && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = common.PreAnalysis{
|
||||
Pod: pod,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "Pod",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Pod.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/viper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func AnalyzePod(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error {
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := client.GetClient().CoreV1().Pods("").List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var brokenPods = map[string][]string{}
|
||||
|
||||
for _, pod := range list.Items {
|
||||
|
||||
// Check for pending pods
|
||||
if pod.Status.Phase == "Pending" {
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, containerStatus := range pod.Status.Conditions {
|
||||
if containerStatus.Type == "PodScheduled" && containerStatus.Reason == "Unschedulable" {
|
||||
brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = []string{containerStatus.Message}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check through container status to check for crashes
|
||||
var failureDetails = []string{}
|
||||
for _, containerStatus := range pod.Status.ContainerStatuses {
|
||||
if containerStatus.State.Waiting != nil {
|
||||
if containerStatus.State.Waiting.Reason == "CrashLoopBackOff" || containerStatus.State.Waiting.Reason == "ImagePullBackOff" {
|
||||
|
||||
failureDetails = append(failureDetails, containerStatus.State.Waiting.Message)
|
||||
brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = failureDetails
|
||||
}
|
||||
// This represents a container that is still being created or blocked due to conditions such as OOMKilled
|
||||
if containerStatus.State.Waiting.Reason == "ContainerCreating" && pod.Status.Phase == "Pending" {
|
||||
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestPodEvent(ctx, client, &pod)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "FailedCreatePodSandBox" {
|
||||
failureDetails = append(failureDetails, evt.Message)
|
||||
brokenPods[fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)] = failureDetails
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
count := 0
|
||||
for key, value := range brokenPods {
|
||||
fmt.Printf("%s: %s: %s\n", color.CyanString("%d", count), color.YellowString(key), color.RedString(value[0]))
|
||||
count++
|
||||
if explain {
|
||||
s := spinner.New(spinner.CharSets[35], 100*time.Millisecond) // Build our new spinner
|
||||
s.Start()
|
||||
|
||||
inputValue := strings.Join(value, " ")
|
||||
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputValue))
|
||||
// find in viper cache
|
||||
if viper.IsSet(sEnc) {
|
||||
s.Stop()
|
||||
// retrieve data from cache
|
||||
response := viper.GetString(sEnc)
|
||||
if response == "" {
|
||||
color.Red("error retrieving cached data")
|
||||
continue
|
||||
}
|
||||
output, err := base64.StdEncoding.DecodeString(response)
|
||||
if err != nil {
|
||||
color.Red("error decoding cached data: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
color.Green(string(output))
|
||||
continue
|
||||
}
|
||||
|
||||
response, err := aiClient.GetCompletion(ctx, inputValue)
|
||||
s.Stop()
|
||||
if err != nil {
|
||||
color.Red("error getting completion: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !viper.IsSet(sEnc) {
|
||||
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
color.Green(response)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
49
pkg/analyzer/pod_test.go
Normal file
49
pkg/analyzer/pod_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestPodAnalyzer(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Reason: "Unschedulable",
|
||||
Message: "0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
podAnalyzer := PodAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := podAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
62
pkg/analyzer/pvc.go
Normal file
62
pkg/analyzer/pvc.go
Normal file
@@ -0,0 +1,62 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type PvcAnalyzer struct{}
|
||||
|
||||
func (PvcAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().PersistentVolumeClaims(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, pvc := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check for empty rs
|
||||
if pvc.Status.Phase == "Pending" {
|
||||
|
||||
// parse the event log and append details
|
||||
evt, err := FetchLatestEvent(a.Context, a.Client, pvc.Namespace, pvc.Name)
|
||||
if err != nil || evt == nil {
|
||||
continue
|
||||
}
|
||||
if evt.Reason == "ProvisioningFailed" && evt.Message != "" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: evt.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)] = common.PreAnalysis{
|
||||
PersistentVolumeClaim: pvc,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.PersistentVolumeClaim.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
60
pkg/analyzer/rs.go
Normal file
60
pkg/analyzer/rs.go
Normal file
@@ -0,0 +1,60 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type ReplicaSetAnalyzer struct{}
|
||||
|
||||
func (ReplicaSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().AppsV1().ReplicaSets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, rs := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check for empty rs
|
||||
if rs.Status.Replicas == 0 {
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, rsStatus := range rs.Status.Conditions {
|
||||
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: rsStatus.Message,
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = common.PreAnalysis{
|
||||
ReplicaSet: rs,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "ReplicaSet",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.ReplicaSet.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/spf13/viper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func AnalyzeReplicaSet(ctx context.Context, client *kubernetes.Client, aiClient *ai.Client, explain bool) error {
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := client.GetClient().AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var brokenRS = map[string][]string{}
|
||||
|
||||
for _, rs := range list.Items {
|
||||
|
||||
// Check for empty rs
|
||||
if rs.Status.Replicas == 0 {
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, rsStatus := range rs.Status.Conditions {
|
||||
if rsStatus.Type == "ReplicaFailure" && rsStatus.Reason == "FailedCreate" {
|
||||
brokenRS[fmt.Sprintf("%s/%s", rs.Namespace, rs.Name)] = []string{rsStatus.Message}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count := 0
|
||||
for key, value := range brokenRS {
|
||||
fmt.Printf("%s: %s: %s\n", color.CyanString("%d", count), color.YellowString(key), color.RedString(value[0]))
|
||||
count++
|
||||
if explain {
|
||||
s := spinner.New(spinner.CharSets[35], 100*time.Millisecond) // Build our new spinner
|
||||
s.Start()
|
||||
|
||||
inputValue := strings.Join(value, " ")
|
||||
|
||||
// Check for cached data
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(inputValue))
|
||||
// find in viper cache
|
||||
if viper.IsSet(sEnc) {
|
||||
s.Stop()
|
||||
// retrieve data from cache
|
||||
response := viper.GetString(sEnc)
|
||||
if response == "" {
|
||||
color.Red("error retrieving cached data")
|
||||
continue
|
||||
}
|
||||
output, err := base64.StdEncoding.DecodeString(response)
|
||||
if err != nil {
|
||||
color.Red("error decoding cached data: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
color.Green(string(output))
|
||||
continue
|
||||
}
|
||||
|
||||
response, err := aiClient.GetCompletion(ctx, inputValue)
|
||||
s.Stop()
|
||||
if err != nil {
|
||||
color.Red("error getting completion: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !viper.IsSet(sEnc) {
|
||||
viper.Set(sEnc, base64.StdEncoding.EncodeToString([]byte(response)))
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
pkg/analyzer/service.go
Normal file
89
pkg/analyzer/service.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ServiceAnalyzer struct{}
|
||||
|
||||
func (ServiceAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
// search all namespaces for pods that are not running
|
||||
list, err := a.Client.GetClient().CoreV1().Endpoints(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, ep := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check for empty service
|
||||
if len(ep.Subsets) == 0 {
|
||||
svc, err := a.Client.GetClient().CoreV1().Services(ep.Namespace).Get(a.Context, ep.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
color.Yellow("Service %s/%s does not exist", ep.Namespace, ep.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range svc.Spec.Selector {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Service has no endpoints, expected label %s=%s", k, v),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: k,
|
||||
Masked: util.MaskString(k),
|
||||
},
|
||||
{
|
||||
Unmasked: v,
|
||||
Masked: util.MaskString(v),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
count := 0
|
||||
pods := []string{}
|
||||
|
||||
// Check through container status to check for crashes
|
||||
for _, epSubset := range ep.Subsets {
|
||||
if len(epSubset.NotReadyAddresses) > 0 {
|
||||
for _, addresses := range epSubset.NotReadyAddresses {
|
||||
count++
|
||||
pods = append(pods, addresses.TargetRef.Kind+"/"+addresses.TargetRef.Name)
|
||||
}
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("Service has not ready endpoints, pods: %s, expected %d", pods, count),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", ep.Namespace, ep.Name)] = common.PreAnalysis{
|
||||
Endpoint: ep,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "Service",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.Endpoint.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
return a.Results, nil
|
||||
}
|
||||
50
pkg/analyzer/service_test.go
Normal file
50
pkg/analyzer/service_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestServiceAnalyzer(t *testing.T) {
|
||||
|
||||
clientset := fake.NewSimpleClientset(&v1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Selector: map[string]string{
|
||||
"app": "example",
|
||||
},
|
||||
}})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
serviceAnalyzer := ServiceAnalyzer{}
|
||||
analysisResults, err := serviceAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
80
pkg/analyzer/statefulset.go
Normal file
80
pkg/analyzer/statefulset.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type StatefulSetAnalyzer struct{}
|
||||
|
||||
func (StatefulSetAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
list, err := a.Client.GetClient().AppsV1().StatefulSets(a.Namespace).List(a.Context, metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, sts := range list.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// get serviceName
|
||||
serviceName := sts.Spec.ServiceName
|
||||
_, err := a.Client.GetClient().CoreV1().Services(sts.Namespace).Get(a.Context, serviceName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("StatefulSet uses the service %s/%s which does not exist.", sts.Namespace, serviceName),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: sts.Namespace,
|
||||
Masked: util.MaskString(sts.Namespace),
|
||||
},
|
||||
{
|
||||
Unmasked: serviceName,
|
||||
Masked: util.MaskString(serviceName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(sts.Spec.VolumeClaimTemplates) > 0 {
|
||||
for _, volumeClaimTemplate := range sts.Spec.VolumeClaimTemplates {
|
||||
if volumeClaimTemplate.Spec.StorageClassName != nil {
|
||||
_, err := a.Client.GetClient().StorageV1().StorageClasses().Get(a.Context, *volumeClaimTemplate.Spec.StorageClassName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("StatefulSet uses the storage class %s which does not exist.", *volumeClaimTemplate.Spec.StorageClassName),
|
||||
Sensitive: []common.Sensitive{
|
||||
{
|
||||
Unmasked: *volumeClaimTemplate.Spec.StorageClassName,
|
||||
Masked: util.MaskString(*volumeClaimTemplate.Spec.StorageClassName),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", sts.Namespace, sts.Name)] = common.PreAnalysis{
|
||||
StatefulSet: sts,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "StatefulSet",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.StatefulSet.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
147
pkg/analyzer/statefulset_test.go
Normal file
147
pkg/analyzer/statefulset_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestStatefulSetAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
},
|
||||
})
|
||||
statefulSetAnalyzer := StatefulSetAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := statefulSetAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
func TestStatefulSetAnalyzerWithoutService(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
ServiceName: "example-svc",
|
||||
},
|
||||
})
|
||||
statefulSetAnalyzer := StatefulSetAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := statefulSetAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var errorFound bool
|
||||
want := "StatefulSet uses the service default/example-svc which does not exist."
|
||||
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetAnalyzerMissingStorageClass(t *testing.T) {
|
||||
storageClassName := "example-sc"
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
ServiceName: "example-svc",
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvc-example",
|
||||
},
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
StorageClassName: &storageClassName,
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
"ReadWriteOnce",
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceStorage: resource.MustParse("1Gi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
statefulSetAnalyzer := StatefulSetAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := statefulSetAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var errorFound bool
|
||||
want := "StatefulSet uses the storage class example-sc which does not exist."
|
||||
|
||||
for _, analysis := range analysisResults {
|
||||
for _, got := range analysis.Error {
|
||||
if want == got.Text {
|
||||
errorFound = true
|
||||
}
|
||||
}
|
||||
if errorFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !errorFound {
|
||||
t.Errorf("Error expected: '%v', not found in StatefulSet's analysis results", want)
|
||||
}
|
||||
|
||||
}
|
||||
59
pkg/common/types.go
Normal file
59
pkg/common/types.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
trivy "github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/ai"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
autov1 "k8s.io/api/autoscaling/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
policyv1 "k8s.io/api/policy/v1"
|
||||
)
|
||||
|
||||
type IAnalyzer interface {
|
||||
Analyze(analysis Analyzer) ([]Result, error)
|
||||
}
|
||||
|
||||
type Analyzer struct {
|
||||
Client *kubernetes.Client
|
||||
Context context.Context
|
||||
Namespace string
|
||||
AIClient ai.IAI
|
||||
PreAnalysis map[string]PreAnalysis
|
||||
Results []Result
|
||||
}
|
||||
|
||||
type PreAnalysis struct {
|
||||
Pod v1.Pod
|
||||
FailureDetails []Failure
|
||||
ReplicaSet appsv1.ReplicaSet
|
||||
PersistentVolumeClaim v1.PersistentVolumeClaim
|
||||
Endpoint v1.Endpoints
|
||||
Ingress networkv1.Ingress
|
||||
HorizontalPodAutoscalers autov1.HorizontalPodAutoscaler
|
||||
PodDisruptionBudget policyv1.PodDisruptionBudget
|
||||
StatefulSet appsv1.StatefulSet
|
||||
// Integrations
|
||||
TrivyVulnerabilityReport trivy.VulnerabilityReport
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
Error []Failure `json:"error"`
|
||||
Details string `json:"details"`
|
||||
ParentObject string `json:"parentObject"`
|
||||
}
|
||||
|
||||
type Failure struct {
|
||||
Text string
|
||||
Sensitive []Sensitive
|
||||
}
|
||||
|
||||
type Sensitive struct {
|
||||
Unmasked string
|
||||
Masked string
|
||||
}
|
||||
113
pkg/integration/integration.go
Normal file
113
pkg/integration/integration.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/integration/trivy"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type IIntegration interface {
|
||||
// Add adds an integration to the cluster
|
||||
Deploy(namespace string) error
|
||||
// Remove removes an integration from the cluster
|
||||
UnDeploy(namespace string) error
|
||||
//
|
||||
AddAnalyzer(*map[string]common.IAnalyzer)
|
||||
// RemoveAnalyzer removes an analyzer from the cluster
|
||||
RemoveAnalyzer() error
|
||||
|
||||
GetAnalyzerName() string
|
||||
|
||||
IsActivate() bool
|
||||
}
|
||||
|
||||
type Integration struct {
|
||||
}
|
||||
|
||||
var integrations = map[string]IIntegration{
|
||||
"trivy": trivy.NewTrivy(),
|
||||
}
|
||||
|
||||
func NewIntegration() *Integration {
|
||||
return &Integration{}
|
||||
}
|
||||
|
||||
func (*Integration) List() []string {
|
||||
keys := make([]string, 0, len(integrations))
|
||||
for k := range integrations {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (*Integration) Get(name string) (IIntegration, error) {
|
||||
if _, ok := integrations[name]; !ok {
|
||||
return nil, errors.New("integration not found")
|
||||
}
|
||||
return integrations[name], nil
|
||||
}
|
||||
|
||||
func (*Integration) Activate(name string, namespace string) error {
|
||||
if _, ok := integrations[name]; !ok {
|
||||
return errors.New("integration not found")
|
||||
}
|
||||
|
||||
if err := integrations[name].Deploy(namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update filters
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
activeFilters = append(activeFilters, integrations[name].GetAnalyzerName())
|
||||
|
||||
viper.Set("active_filters", activeFilters)
|
||||
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Integration) Deactivate(name string, namespace string) error {
|
||||
if _, ok := integrations[name]; !ok {
|
||||
return errors.New("integration not found")
|
||||
}
|
||||
|
||||
if err := integrations[name].UnDeploy(namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update filters
|
||||
// This might be a bad idea, but we cannot reference analyzer here
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
|
||||
// Remove filter
|
||||
for i, v := range activeFilters {
|
||||
if v == integrations[name].GetAnalyzerName() {
|
||||
activeFilters = append(activeFilters[:i], activeFilters[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
viper.Set("active_filters", activeFilters)
|
||||
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
color.Red("Error writing config file: %s", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Integration) IsActivate(name string) (bool, error) {
|
||||
if _, ok := integrations[name]; !ok {
|
||||
return false, errors.New("integration not found")
|
||||
}
|
||||
return integrations[name].IsActivate(), nil
|
||||
}
|
||||
74
pkg/integration/trivy/analyzer.go
Normal file
74
pkg/integration/trivy/analyzer.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package trivy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type TrivyAnalyzer struct {
|
||||
}
|
||||
|
||||
func (TrivyAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
// Get all trivy VulnerabilityReports
|
||||
result := &v1alpha1.VulnerabilityReportList{}
|
||||
|
||||
config := a.Client.GetConfig()
|
||||
// Add group version to sceheme
|
||||
config.ContentConfig.GroupVersion = &v1alpha1.SchemeGroupVersion
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
config.APIPath = "/apis"
|
||||
|
||||
restClient, err := rest.UnversionedRESTClientFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = restClient.Get().Resource("vulnerabilityreports").Do(a.Context).Into(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Find criticals and get CVE
|
||||
var preAnalysis = map[string]common.PreAnalysis{}
|
||||
|
||||
for _, report := range result.Items {
|
||||
|
||||
// For each pod there may be multiple vulnerabilities
|
||||
var failures []common.Failure
|
||||
for _, vuln := range report.Report.Vulnerabilities {
|
||||
if vuln.Severity == "CRITICAL" {
|
||||
// get the vulnerability ID
|
||||
// get the vulnerability description
|
||||
failures = append(failures, common.Failure{
|
||||
Text: fmt.Sprintf("critical Vulnerability found ID: %s (learn more at: %s)", vuln.VulnerabilityID, vuln.PrimaryLink),
|
||||
Sensitive: []common.Sensitive{},
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", report.Labels["trivy-operator.resource.namespace"],
|
||||
report.Labels["trivy-operator.resource.name"])] = common.PreAnalysis{
|
||||
TrivyVulnerabilityReport: report,
|
||||
FailureDetails: failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
var currentAnalysis = common.Result{
|
||||
Kind: "VulnerabilityReport",
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
|
||||
parent, _ := util.GetParent(a.Client, value.TrivyVulnerabilityReport.ObjectMeta)
|
||||
currentAnalysis.ParentObject = parent
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
}
|
||||
103
pkg/integration/trivy/trivy.go
Normal file
103
pkg/integration/trivy/trivy.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package trivy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
helmclient "github.com/mittwald/go-helm-client"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
const (
|
||||
Repo = "https://aquasecurity.github.io/helm-charts/"
|
||||
Version = "0.13.0"
|
||||
ChartName = "trivy-operator"
|
||||
RepoShortName = "aqua"
|
||||
ReleaseName = "trivy-operator-k8sgpt"
|
||||
)
|
||||
|
||||
type Trivy struct {
|
||||
helm helmclient.Client
|
||||
}
|
||||
|
||||
func NewTrivy() *Trivy {
|
||||
helmClient, err := helmclient.New(&helmclient.Options{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &Trivy{
|
||||
helm: helmClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trivy) GetAnalyzerName() string {
|
||||
return "VulnerabilityReport"
|
||||
}
|
||||
|
||||
func (t *Trivy) Deploy(namespace string) error {
|
||||
|
||||
// Add the repository
|
||||
chartRepo := repo.Entry{
|
||||
Name: RepoShortName,
|
||||
URL: Repo,
|
||||
}
|
||||
|
||||
// Add a chart-repository to the client.
|
||||
if err := t.helm.AddOrUpdateChartRepo(chartRepo); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
chartSpec := helmclient.ChartSpec{
|
||||
ReleaseName: ReleaseName,
|
||||
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
|
||||
Namespace: namespace,
|
||||
UpgradeCRDs: true,
|
||||
Wait: false,
|
||||
Timeout: 300,
|
||||
}
|
||||
|
||||
// Install a chart release.
|
||||
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
|
||||
if _, err := t.helm.InstallOrUpgradeChart(context.Background(), &chartSpec, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Trivy) UnDeploy(namespace string) error {
|
||||
chartSpec := helmclient.ChartSpec{
|
||||
ReleaseName: ReleaseName,
|
||||
ChartName: fmt.Sprintf("%s/%s", RepoShortName, ChartName),
|
||||
Namespace: namespace,
|
||||
UpgradeCRDs: true,
|
||||
Wait: false,
|
||||
Timeout: 300,
|
||||
}
|
||||
// Uninstall the chart release.
|
||||
// Note that helmclient.Options.Namespace should ideally match the namespace in chartSpec.Namespace.
|
||||
if err := t.helm.UninstallRelease(&chartSpec); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Trivy) IsActivate() bool {
|
||||
|
||||
if _, err := t.helm.GetRelease(ReleaseName); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Trivy) AddAnalyzer(mergedMap *map[string]common.IAnalyzer) {
|
||||
|
||||
(*mergedMap)["VulnerabilityReport"] = &TrivyAnalyzer{}
|
||||
|
||||
}
|
||||
|
||||
func (t *Trivy) RemoveAnalyzer() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +1,59 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
client *kubernetes.Clientset
|
||||
Client kubernetes.Interface
|
||||
RestClient rest.Interface
|
||||
Config *rest.Config
|
||||
}
|
||||
|
||||
func (c *Client) GetClient() *kubernetes.Clientset {
|
||||
return c.client
|
||||
func (c *Client) GetConfig() *rest.Config {
|
||||
return c.Config
|
||||
}
|
||||
|
||||
func NewClient(masterURL string, kubeconfig string) (*Client, error) {
|
||||
func (c *Client) GetClient() kubernetes.Interface {
|
||||
return c.Client
|
||||
}
|
||||
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
kubeconfig :=
|
||||
clientcmd.NewDefaultClientConfigLoadingRules().GetDefaultFilename()
|
||||
config, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(config)
|
||||
func (c *Client) GetRestClient() rest.Interface {
|
||||
return c.RestClient
|
||||
}
|
||||
|
||||
func NewClient(kubecontext string, kubeconfig string) (*Client, error) {
|
||||
|
||||
config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},
|
||||
&clientcmd.ConfigOverrides{
|
||||
CurrentContext: kubecontext,
|
||||
})
|
||||
// create the clientset
|
||||
c, err := config.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientSet, err := kubernetes.NewForConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.APIPath = "/api"
|
||||
c.GroupVersion = &scheme.Scheme.PrioritizedVersionsForGroup("")[0]
|
||||
c.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
restClient, err := rest.RESTClientFor(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
client: clientSet,
|
||||
Client: clientSet,
|
||||
RestClient: restClient,
|
||||
Config: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
131
pkg/util/util.go
Normal file
131
pkg/util/util.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var anonymizePattern = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{}|;':\",./<>?")
|
||||
|
||||
func SliceContainsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetParent(client *kubernetes.Client, meta metav1.ObjectMeta) (string, bool) {
|
||||
if meta.OwnerReferences != nil {
|
||||
for _, owner := range meta.OwnerReferences {
|
||||
switch owner.Kind {
|
||||
case "ReplicaSet":
|
||||
rs, err := client.GetClient().AppsV1().ReplicaSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if rs.OwnerReferences != nil {
|
||||
return GetParent(client, rs.ObjectMeta)
|
||||
}
|
||||
return "ReplicaSet/" + rs.Name, false
|
||||
|
||||
case "Deployment":
|
||||
dep, err := client.GetClient().AppsV1().Deployments(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if dep.OwnerReferences != nil {
|
||||
return GetParent(client, dep.ObjectMeta)
|
||||
}
|
||||
return "Deployment/" + dep.Name, false
|
||||
|
||||
case "StatefulSet":
|
||||
sts, err := client.GetClient().AppsV1().StatefulSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if sts.OwnerReferences != nil {
|
||||
return GetParent(client, sts.ObjectMeta)
|
||||
}
|
||||
return "StatefulSet/" + sts.Name, false
|
||||
|
||||
case "DaemonSet":
|
||||
ds, err := client.GetClient().AppsV1().DaemonSets(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if ds.OwnerReferences != nil {
|
||||
return GetParent(client, ds.ObjectMeta)
|
||||
}
|
||||
return "DaemonSet/" + ds.Name, false
|
||||
|
||||
case "Ingress":
|
||||
ds, err := client.GetClient().NetworkingV1().Ingresses(meta.Namespace).Get(context.Background(), owner.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if ds.OwnerReferences != nil {
|
||||
return GetParent(client, ds.ObjectMeta)
|
||||
}
|
||||
return "Ingress/" + ds.Name, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return meta.Name, false
|
||||
}
|
||||
|
||||
func RemoveDuplicates(slice []string) ([]string, []string) {
|
||||
set := make(map[string]bool)
|
||||
duplicates := []string{}
|
||||
for _, val := range slice {
|
||||
if _, ok := set[val]; !ok {
|
||||
set[val] = true
|
||||
} else {
|
||||
duplicates = append(duplicates, val)
|
||||
}
|
||||
}
|
||||
uniqueSlice := make([]string, 0, len(set))
|
||||
for val := range set {
|
||||
uniqueSlice = append(uniqueSlice, val)
|
||||
}
|
||||
return uniqueSlice, duplicates
|
||||
}
|
||||
|
||||
func SliceDiff(source, dest []string) []string {
|
||||
mb := make(map[string]struct{}, len(dest))
|
||||
for _, x := range dest {
|
||||
mb[x] = struct{}{}
|
||||
}
|
||||
var diff []string
|
||||
for _, x := range source {
|
||||
if _, found := mb[x]; !found {
|
||||
diff = append(diff, x)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func MaskString(input string) string {
|
||||
key := make([]byte, len(input))
|
||||
result := make([]rune, len(input))
|
||||
rand.Read(key)
|
||||
for i := range result {
|
||||
result[i] = anonymizePattern[int(key[i])%len(anonymizePattern)]
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString([]byte(string(result)))
|
||||
}
|
||||
|
||||
func ReplaceIfMatch(text string, pattern string, replacement string) string {
|
||||
re := regexp.MustCompile(fmt.Sprintf(`%s(\b)`, pattern))
|
||||
if re.MatchString(text) {
|
||||
text = re.ReplaceAllString(text, replacement)
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -2,7 +2,14 @@
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
"helpers:pinGitHubActionDigests"
|
||||
"helpers:pinGitHubActionDigests",
|
||||
":gitSignOff"
|
||||
|
||||
],
|
||||
"addLabels": ["dependencies"],
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy",
|
||||
"gomodMassage"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
@@ -10,9 +17,31 @@
|
||||
"matchCurrentVersion": "!/^0/",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchManagers": ["gomod"],
|
||||
"addLabels": ["go"]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"automerge": true
|
||||
"addLabels": ["github_actions"]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["dockerfile"],
|
||||
"addLabels": ["docker"]
|
||||
}
|
||||
],
|
||||
"regexManagers": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"(^|\\/)Makefile$",
|
||||
"(^|\\/)Dockerfile",
|
||||
"(^|\\/).*\\.ya?ml$",
|
||||
"(^|\\/).*\\.toml$",
|
||||
"(^|\\/).*\\.sh$"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=(?<datasource>.+?) depName=(?<depName>.+?)\\s.*?_VERSION ?(\\??=|\\: ?) ?\\\"?(?<currentValue>.+?)?\\\"?\\s"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user