mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 11:33:08 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5db4bc28a7 | ||
|
|
8f8f5c6df7 | ||
|
|
3c1c055ac7 | ||
|
|
ebfbba98ca | ||
|
|
47463d4412 | ||
|
|
fe81d16f75 | ||
|
|
a1d0d0a180 | ||
|
|
f60467cd4d | ||
|
|
20892b48d0 |
9
.github/workflows/test.yaml
vendored
9
.github/workflows/test.yaml
vendored
@@ -9,11 +9,10 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.21"
|
||||
GO_VERSION: "~1.21"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||
@@ -24,4 +23,8 @@ jobs:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Run test
|
||||
run: go test ./...
|
||||
run: go test ./... -coverprofile=coverage.txt
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.3.28"}
|
||||
{".":"0.3.29"}
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.29](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.28...v0.3.29) (2024-03-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* codecov ([#1023](https://github.com/k8sgpt-ai/k8sgpt/issues/1023)) ([fe81d16](https://github.com/k8sgpt-ai/k8sgpt/commit/fe81d16f756e5ea9db909e42e6caf1e17e040f86))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* allows an environmental override of the default AWS region and… ([#1025](https://github.com/k8sgpt-ai/k8sgpt/issues/1025)) ([8f8f5c6](https://github.com/k8sgpt-ai/k8sgpt/commit/8f8f5c6df7fbcd08ee48d91a4f2e011a3e69e4ac))
|
||||
|
||||
## [0.3.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.3.27...v0.3.28) (2024-03-14)
|
||||
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -41,7 +41,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_386.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_386.rpm
|
||||
sudo rpm -ivh k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -50,7 +50,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_amd64.rpm
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh -i k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -62,7 +62,7 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -70,7 +70,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -83,14 +83,14 @@ brew install k8sgpt
|
||||
**32 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_386.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_386.apk
|
||||
apk add k8sgpt_386.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
**64 bit:**
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.28/k8sgpt_amd64.apk
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.3.29/k8sgpt_amd64.apk
|
||||
apk add k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->x
|
||||
|
||||
@@ -45,6 +45,9 @@ var addCmd = &cobra.Command{
|
||||
_ = cmd.MarkFlagRequired("endpointname")
|
||||
_ = cmd.MarkFlagRequired("providerRegion")
|
||||
}
|
||||
if strings.ToLower(backend) == "amazonbedrock" {
|
||||
_ = cmd.MarkFlagRequired("providerRegion")
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@@ -76,6 +77,9 @@ func GetModelOrDefault(model string) string {
|
||||
// GetModelOrDefault check config region
|
||||
func GetRegionOrDefault(region string) string {
|
||||
|
||||
if os.Getenv("AWS_DEFAULT_REGION") != "" {
|
||||
region = os.Getenv("AWS_DEFAULT_REGION")
|
||||
}
|
||||
// Check if the provided model is in the list
|
||||
for _, m := range BEDROCKER_SUPPORTED_REGION {
|
||||
if m == region {
|
||||
|
||||
@@ -123,15 +123,15 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
|
||||
AnalyzerErrorsMetric.WithLabelValues(kind, cronJob.Name, cronJob.Namespace).Set(float64(len(failures)))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range preAnalysis {
|
||||
currentAnalysis := common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
for key, value := range preAnalysis {
|
||||
currentAnalysis := common.Result{
|
||||
Kind: kind,
|
||||
Name: key,
|
||||
Error: value.FailureDetails,
|
||||
}
|
||||
a.Results = append(a.Results, currentAnalysis)
|
||||
}
|
||||
|
||||
return a.Results, nil
|
||||
|
||||
@@ -15,219 +15,144 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestCronJobSuccess(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*/1 * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
func TestCronJobAnalyzer(t *testing.T) {
|
||||
suspend := new(bool)
|
||||
*suspend = true
|
||||
|
||||
invalidStartingDeadline := new(int64)
|
||||
*invalidStartingDeadline = -7
|
||||
|
||||
validStartingDeadline := new(int64)
|
||||
*validStartingDeadline = 7
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
}
|
||||
|
||||
func TestCronJobBroken(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
|
||||
assert.Equal(t, analysisResults[0].Kind, "CronJob")
|
||||
}
|
||||
|
||||
func TestCronJobBrokenMultipleNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ1",
|
||||
// This CronJob won't be list because of namespace filtering.
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ2",
|
||||
Namespace: "default",
|
||||
},
|
||||
// A suspended CronJob will contribute to failures.
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Suspend: suspend,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ3",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Valid schedule
|
||||
Schedule: "*/1 * * * *",
|
||||
|
||||
// Negative starting deadline
|
||||
StartingDeadlineSeconds: invalidStartingDeadline,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ4",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Invalid schedule
|
||||
Schedule: "*** * * * *",
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "CJ5",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
// Valid schedule
|
||||
Schedule: "*/1 * * * *",
|
||||
|
||||
// Positive starting deadline shouldn't be any problem.
|
||||
StartingDeadlineSeconds: validStartingDeadline,
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
// This cronjob shouldn't contribute to any failures.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "successful-cronjob",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*/1 * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-cronjob",
|
||||
Namespace: "other-namespace",
|
||||
Annotations: map[string]string{
|
||||
"analysisDate": "2022-04-01",
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*** * * * *",
|
||||
ConcurrencyPolicy: "Allow",
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app": "example-app",
|
||||
},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "example-container",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
RestartPolicy: v1.RestartPolicyOnFailure,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analyzer := CronJobAnalyzer{}
|
||||
analysisResults, err := analyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
cjAnalyzer := CronJobAnalyzer{}
|
||||
results, err := cjAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
expectations := []string{
|
||||
"default/CJ2",
|
||||
"default/CJ3",
|
||||
"default/CJ4",
|
||||
}
|
||||
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
assert.Equal(t, analysisResults[0].Name, "default/example-cronjob")
|
||||
assert.Equal(t, analysisResults[0].Kind, "CronJob")
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i], result.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,146 +15,189 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestIngressAnalyzer(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
validIgClassName := new(string)
|
||||
*validIgClassName = "valid-ingress-class"
|
||||
|
||||
var igRule networkingv1.IngressRule
|
||||
|
||||
httpRule := networkingv1.HTTPIngressRuleValue{
|
||||
Paths: []networkingv1.HTTPIngressPath{
|
||||
{
|
||||
Path: "/",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service exists.
|
||||
Name: "Service1",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
{
|
||||
Path: "/test1",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service is in the test namespace
|
||||
// Hence, it won't be discovered.
|
||||
Name: "Service2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Path: "/test2",
|
||||
Backend: networkingv1.IngressBackend{
|
||||
Service: &networkingv1.IngressServiceBackend{
|
||||
// This service doesn't exist.
|
||||
Name: "Service3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
igRule.IngressRuleValue.HTTP = &httpRule
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
Client: fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
// Doesn't specify an ingress class.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress2",
|
||||
Namespace: "default",
|
||||
// Specify an invalid ingress class name using annotations.
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "invalid-class",
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
// Namespace filtering.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress3",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&networkingv1.IngressClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: *validIgClassName,
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress4",
|
||||
Namespace: "default",
|
||||
// Specify valid ingress class name using annotations.
|
||||
Annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": *validIgClassName,
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Service1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Namespace filtering.
|
||||
Name: "Service2",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Secret1",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
&v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Secret2",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Ingress5",
|
||||
Namespace: "default",
|
||||
},
|
||||
|
||||
func TestIngressAnalyzerWithMultipleIngresses(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example-2",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
)
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
// Specify valid ingress class name in spec.
|
||||
Spec: networkingv1.IngressSpec{
|
||||
IngressClassName: validIgClassName,
|
||||
Rules: []networkingv1.IngressRule{
|
||||
igRule,
|
||||
},
|
||||
TLS: []networkingv1.IngressTLS{
|
||||
{
|
||||
// This won't contribute to any failures.
|
||||
SecretName: "Secret1",
|
||||
},
|
||||
{
|
||||
// This secret won't be discovered because of namespace filtering.
|
||||
SecretName: "Secret2",
|
||||
},
|
||||
{
|
||||
// This secret doesn't exist.
|
||||
SecretName: "Secret3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 2)
|
||||
}
|
||||
igAnalyzer := IngressAnalyzer{}
|
||||
results, err := igAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
func TestIngressAnalyzerWithoutIngressClassAnnotation(t *testing.T) {
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
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,
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/Ingress1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/Ingress2",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/Ingress5",
|
||||
failuresCount: 4,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
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")
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressAnalyzerNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
},
|
||||
&networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
Namespace: "other-namespace",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
})
|
||||
ingressAnalyzer := IngressAnalyzer{}
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "default",
|
||||
}
|
||||
analysisResults, err := ingressAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -136,6 +136,19 @@ func TestNetpolWithPod(t *testing.T) {
|
||||
|
||||
func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "policy-without-podselector-match-labels",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PodSelector: metav1.LabelSelector{
|
||||
// len(MatchLabels) == 0 should trigger a failure.
|
||||
// Allowing traffic to all pods.
|
||||
MatchLabels: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
&networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "example",
|
||||
@@ -203,7 +216,7 @@ func TestNetpolNoPodsNamespaceFiltering(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(results), 1)
|
||||
assert.Equal(t, len(results), 2)
|
||||
assert.Equal(t, results[0].Kind, "NetworkPolicy")
|
||||
|
||||
}
|
||||
|
||||
@@ -15,110 +15,155 @@ package analyzer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
"github.com/magiconair/properties/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestNodeAnalyzerNodeReady(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "KubeletReady",
|
||||
Message: "kubelet is posting ready status",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
func TestNodeAnalyzer(t *testing.T) {
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 0)
|
||||
}
|
||||
|
||||
func TestNodeAnalyzerNodeDiskPressure(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: "KubeletHasDiskPressure",
|
||||
Message: "kubelet has disk pressure",
|
||||
Client: fake.NewSimpleClientset(
|
||||
&v1.Node{
|
||||
// A node without Status Conditions shouldn't contribute to failures.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node1",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Node{
|
||||
// Nodes are not filtered using namespace.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
// Won't contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
// Will contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
// Will contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
// Non-false statuses for the default cases contribute to failures.
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionUnknown,
|
||||
},
|
||||
// A cloud provider may set their own condition and/or a new status
|
||||
// might be introduced. In such cases a failure is assumed and
|
||||
// the code shouldn't break, although it might be a false positive.
|
||||
{
|
||||
Type: "UnknownNodeConditionType",
|
||||
Status: "CompletelyUnknown",
|
||||
},
|
||||
// These won't contribute to failures.
|
||||
{
|
||||
Type: v1.NodeMemoryPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeDiskPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodePIDPressure,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
{
|
||||
Type: v1.NodeNetworkUnavailable,
|
||||
Status: v1.ConditionFalse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&v1.Node{
|
||||
// A node without any failures shouldn't be present in the results.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Node3",
|
||||
Namespace: "test",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
// Won't contribute to failures.
|
||||
Type: v1.NodeReady,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
Context: context.Background(),
|
||||
Namespace: "test",
|
||||
}
|
||||
|
||||
nAnalyzer := NodeAnalyzer{}
|
||||
results, err := nAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Name < results[j].Name
|
||||
})
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "Node2",
|
||||
failuresCount: 11,
|
||||
},
|
||||
Context: context.Background(),
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
// A cloud provider may set their own condition and/or a new status might be introduced
|
||||
// In such cases a failure is assumed and the code shouldn't break, although it might be a false positive
|
||||
func TestNodeAnalyzerNodeUnknownType(t *testing.T) {
|
||||
clientset := fake.NewSimpleClientset(&v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node1",
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Conditions: []v1.NodeCondition{
|
||||
{
|
||||
Type: "UnknownNodeConditionType",
|
||||
Status: "CompletelyUnknown",
|
||||
Reason: "KubeletHasTheUnknown",
|
||||
Message: "kubelet has the unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
config := common.Analyzer{
|
||||
Client: &kubernetes.Client{
|
||||
Client: clientset,
|
||||
},
|
||||
Context: context.Background(),
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
nodeAnalyzer := NodeAnalyzer{}
|
||||
var analysisResults []common.Result
|
||||
analysisResults, err := nodeAnalyzer.Analyze(config)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, len(analysisResults), 1)
|
||||
}
|
||||
|
||||
@@ -112,11 +112,6 @@ func TestPodDisruptionBudgetAnalyzer(t *testing.T) {
|
||||
pdbAnalyzer := PdbAnalyzer{}
|
||||
results, err := pdbAnalyzer.Analyze(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, result := range results {
|
||||
require.Equal(t, "test/PDB3", result.Name)
|
||||
for _, failure := range result.Error {
|
||||
require.Contains(t, failure.Text, "expected pdb pod label")
|
||||
}
|
||||
}
|
||||
require.Equal(t, 1, len(results))
|
||||
require.Equal(t, "test/PDB3", results[0].Name)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/kubernetes"
|
||||
@@ -38,13 +39,15 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
Client: &kubernetes.Client{
|
||||
Client: fake.NewSimpleClientset(
|
||||
&appsv1.Event{
|
||||
// This is the latest event.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event1",
|
||||
Namespace: "default",
|
||||
},
|
||||
LastTimestamp: metav1.Time{
|
||||
Time: time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Reason: "ProvisioningFailed",
|
||||
Message: "PVC provisioning failed",
|
||||
Message: "PVC Event1 provisioning failed",
|
||||
},
|
||||
&appsv1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -54,10 +57,16 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
&appsv1.Event{
|
||||
// This is the latest event.
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "Event3",
|
||||
Namespace: "default",
|
||||
},
|
||||
LastTimestamp: metav1.Time{
|
||||
Time: time.Date(2024, 4, 15, 10, 0, 0, 0, time.UTC),
|
||||
},
|
||||
Reason: "ProvisioningFailed",
|
||||
Message: "PVC Event3 provisioning failed",
|
||||
},
|
||||
&appsv1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -214,9 +223,6 @@ func TestPersistentVolumeClaimAnalyzer(t *testing.T) {
|
||||
|
||||
for i, expectation := range tt.expectations {
|
||||
require.Equal(t, expectation, results[i].Name)
|
||||
for _, failure := range results[i].Error {
|
||||
require.Equal(t, "PVC provisioning failed", failure.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -124,30 +124,23 @@ func TestReplicaSetAnalyzer(t *testing.T) {
|
||||
})
|
||||
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresText []string
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "default/ReplicaSet1",
|
||||
failuresText: []string{
|
||||
"failed to create test replica set 1",
|
||||
},
|
||||
name: "default/ReplicaSet1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "default/ReplicaSet4",
|
||||
failuresText: []string{
|
||||
"failed to create test replica set 4 condition 1",
|
||||
"failed to create test replica set 4 condition 3",
|
||||
},
|
||||
name: "default/ReplicaSet4",
|
||||
failuresCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, len(expectations), len(results))
|
||||
|
||||
for i, expectation := range expectations {
|
||||
require.Equal(t, expectation.name, results[i].Name)
|
||||
for j, failure := range results[i].Error {
|
||||
require.Equal(t, expectation.failuresText[j], failure.Text)
|
||||
}
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,21 +145,16 @@ func TestServiceAnalyzer(t *testing.T) {
|
||||
})
|
||||
|
||||
expectations := []struct {
|
||||
name string
|
||||
failuresText []string
|
||||
name string
|
||||
failuresCount int
|
||||
}{
|
||||
{
|
||||
name: "test/Endpoint1",
|
||||
failuresText: []string{
|
||||
"Service has not ready endpoints, pods",
|
||||
},
|
||||
name: "test/Endpoint1",
|
||||
failuresCount: 1,
|
||||
},
|
||||
{
|
||||
name: "test/Service1",
|
||||
failuresText: []string{
|
||||
"Service has no endpoints, expected label",
|
||||
"Service has no endpoints, expected label",
|
||||
},
|
||||
name: "test/Service1",
|
||||
failuresCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -167,8 +162,6 @@ func TestServiceAnalyzer(t *testing.T) {
|
||||
|
||||
for i, result := range results {
|
||||
require.Equal(t, expectations[i].name, result.Name)
|
||||
for j, failure := range result.Error {
|
||||
require.Contains(t, failure.Text, expectations[i].failuresText[j])
|
||||
}
|
||||
require.Equal(t, expectations[i].failuresCount, len(result.Error))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 The K8sGPT Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func TestSliceContainsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeContext string
|
||||
kubeConfig string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "empty config and empty context",
|
||||
kubeContext: "",
|
||||
kubeConfig: "",
|
||||
expectedErr: "invalid configuration: no configuration has been provided, try setting KUBERNETES_MASTER environment variable",
|
||||
},
|
||||
{
|
||||
name: "non empty config and empty context",
|
||||
kubeContext: "",
|
||||
kubeConfig: "kube-config",
|
||||
expectedErr: "stat kube-config: no such file or directory",
|
||||
},
|
||||
{
|
||||
name: "empty config and non empty context",
|
||||
kubeContext: "some-context",
|
||||
kubeConfig: "",
|
||||
expectedErr: "context \"some-context\" does not exist",
|
||||
},
|
||||
{
|
||||
name: "non empty config and non empty context",
|
||||
kubeContext: "minikube",
|
||||
kubeConfig: "./testdata/kubeconfig",
|
||||
expectedErr: "Get \"https://192.168.49.2:8443/version\": dial tcp 192.168.49.2:8443",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client, err := NewClient(tt.kubeContext, tt.kubeConfig)
|
||||
if tt.expectedErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, tt.expectedErr)
|
||||
require.Nil(t, client)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesClient(t *testing.T) {
|
||||
client := Client{
|
||||
Config: &rest.Config{
|
||||
Host: "host",
|
||||
},
|
||||
}
|
||||
|
||||
require.NotEmpty(t, client.GetConfig())
|
||||
require.Nil(t, client.GetClient())
|
||||
require.Nil(t, client.GetCtrlClient())
|
||||
}
|
||||
@@ -12,6 +12,9 @@
|
||||
],
|
||||
"automerge": true,
|
||||
"automergeType": "pr",
|
||||
"schedule": [
|
||||
"at any time"
|
||||
],
|
||||
"platformAutomerge": true,
|
||||
"packageRules": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user