mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-19 03:23:47 +00:00
Compare commits
3 Commits
main
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b92512aec0 | ||
|
|
52f7808a3a | ||
|
|
1f5d9a8fe0 |
@@ -42,7 +42,7 @@ var (
|
||||
mcpPort string
|
||||
mcpHTTP bool
|
||||
// filters can be injected into the server (repeatable flag)
|
||||
filters []string
|
||||
filters []string
|
||||
)
|
||||
|
||||
var ServeCmd = &cobra.Command{
|
||||
|
||||
@@ -20,11 +20,11 @@ type MockBedrockClient struct {
|
||||
|
||||
func (m *MockBedrockClient) GetInferenceProfile(ctx context.Context, params *bedrock.GetInferenceProfileInput, optFns ...func(*bedrock.Options)) (*bedrock.GetInferenceProfileOutput, error) {
|
||||
args := m.Called(ctx, params)
|
||||
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
return args.Get(0).(*bedrock.GetInferenceProfileOutput), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ type MockBedrockRuntimeClient struct {
|
||||
|
||||
func (m *MockBedrockRuntimeClient) InvokeModel(ctx context.Context, params *bedrockruntime.InvokeModelInput, optFns ...func(*bedrockruntime.Options)) (*bedrockruntime.InvokeModelOutput, error) {
|
||||
args := m.Called(ctx, params)
|
||||
|
||||
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
|
||||
return args.Get(0).(*bedrockruntime.InvokeModelOutput), args.Error(1)
|
||||
}
|
||||
|
||||
@@ -59,21 +59,21 @@ func TestBedrockInferenceProfileARNWithMocks(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// Create a client with test models
|
||||
client := &AmazonBedRockClient{models: testModels}
|
||||
|
||||
|
||||
// Create mock clients
|
||||
mockMgmtClient := new(MockBedrockClient)
|
||||
mockRuntimeClient := new(MockBedrockRuntimeClient)
|
||||
|
||||
|
||||
// Inject mock clients into the AmazonBedRockClient
|
||||
client.mgmtClient = mockMgmtClient
|
||||
client.client = mockRuntimeClient
|
||||
|
||||
|
||||
// Test with a valid inference profile ARN
|
||||
inferenceProfileARN := "arn:aws:bedrock:us-east-1:123456789012:inference-profile/my-profile"
|
||||
|
||||
|
||||
// Setup mock response for GetInferenceProfile
|
||||
mockMgmtClient.On("GetInferenceProfile", mock.Anything, &bedrock.GetInferenceProfileInput{
|
||||
InferenceProfileIdentifier: aws.String("my-profile"),
|
||||
@@ -84,20 +84,20 @@ func TestBedrockInferenceProfileARNWithMocks(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
|
||||
// Configure the client with the inference profile ARN
|
||||
config := AIProvider{
|
||||
Model: inferenceProfileARN,
|
||||
ProviderRegion: "us-east-1",
|
||||
}
|
||||
|
||||
|
||||
// Test the Configure method with the inference profile ARN
|
||||
err := client.Configure(&config)
|
||||
|
||||
|
||||
// Verify that the configuration was successful
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, inferenceProfileARN, client.model.Config.ModelName)
|
||||
|
||||
|
||||
// Verify that the mock was called
|
||||
mockMgmtClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (p *ViperConfigProvider) UnmarshalKey(key string, rawVal interface{}) error
|
||||
|
||||
// Default instances to be used
|
||||
var (
|
||||
DefaultClientFactory = &DefaultAIClientFactory{}
|
||||
DefaultClientFactory = &DefaultAIClientFactory{}
|
||||
DefaultConfigProvider = &ViperConfigProvider{}
|
||||
)
|
||||
|
||||
@@ -84,4 +84,4 @@ func SetTestConfigProvider(provider ConfigProvider) {
|
||||
func ResetTestImplementations() {
|
||||
testAIClientFactory = nil
|
||||
testConfigProvider = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +94,11 @@ func (c *OpenAIClient) GetCompletion(ctx context.Context, prompt string) (string
|
||||
Content: prompt,
|
||||
},
|
||||
},
|
||||
Temperature: c.temperature,
|
||||
Temperature: c.temperature,
|
||||
MaxCompletionTokens: maxToken,
|
||||
PresencePenalty: presencePenalty,
|
||||
FrequencyPenalty: frequencyPenalty,
|
||||
TopP: c.topP,
|
||||
PresencePenalty: presencePenalty,
|
||||
FrequencyPenalty: frequencyPenalty,
|
||||
TopP: c.topP,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/common"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -80,6 +81,17 @@ func (ConfigMapAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
for _, cm := range configMaps.Items {
|
||||
var failures []common.Failure
|
||||
|
||||
// Check if ConfigMap is dynamically loaded by sidecars
|
||||
if isKnownSidecarPattern(cm) {
|
||||
usedConfigMaps[cm.Name] = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if usage check should be skipped
|
||||
if shouldSkipUsageCheck(cm) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for unused ConfigMaps
|
||||
if !usedConfigMaps[cm.Name] {
|
||||
failures = append(failures, common.Failure{
|
||||
@@ -123,3 +135,33 @@ func (ConfigMapAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// isKnownSidecarPattern detects ConfigMaps that are dynamically loaded by sidecar containers
|
||||
// These ConfigMaps are not directly referenced in Pod specs but are watched via Kubernetes API
|
||||
func isKnownSidecarPattern(cm v1.ConfigMap) bool {
|
||||
// Common sidecar patterns
|
||||
knownLabels := []string{
|
||||
"grafana_dashboard", // Grafana sidecar dashboard loader
|
||||
"grafana_datasource", // Grafana sidecar datasource loader
|
||||
"prometheus_rule", // Prometheus operator rule loader
|
||||
"fluentd_config", // Fluentd config reloader
|
||||
}
|
||||
|
||||
for _, label := range knownLabels {
|
||||
if _, exists := cm.Labels[label]; exists {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// User-defined marker for dynamically loaded ConfigMaps
|
||||
if cm.Labels["k8sgpt.ai/dynamically-loaded"] == "true" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldSkipUsageCheck allows users to opt-out of usage checking
|
||||
func shouldSkipUsageCheck(cm v1.ConfigMap) bool {
|
||||
return cm.Annotations["k8sgpt.ai/skip-usage-check"] == "true"
|
||||
}
|
||||
|
||||
@@ -147,3 +147,134 @@ func TestConfigMapAnalyzer(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigMapAnalyzer_SidecarPatterns tests known sidecar patterns and skip annotations
|
||||
func TestConfigMapAnalyzer_SidecarPatterns(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
configMaps []v1.ConfigMap
|
||||
pods []v1.Pod
|
||||
expectedErrors int
|
||||
}{
|
||||
{
|
||||
name: "grafana dashboard configmap should not be flagged as unused",
|
||||
namespace: "monitoring",
|
||||
configMaps: []v1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "grafana-dashboard",
|
||||
Namespace: "monitoring",
|
||||
Labels: map[string]string{
|
||||
"grafana_dashboard": "1",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"dashboard.json": `{"title": "My Dashboard"}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
pods: []v1.Pod{},
|
||||
expectedErrors: 0,
|
||||
},
|
||||
{
|
||||
name: "configmap with skip annotation should be ignored",
|
||||
namespace: "default",
|
||||
configMaps: []v1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ignored-cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"k8sgpt.ai/skip-usage-check": "true",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 0,
|
||||
},
|
||||
{
|
||||
name: "normal unused configmap should still be flagged",
|
||||
namespace: "default",
|
||||
configMaps: []v1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "unused-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "prometheus rule configmap should not be flagged",
|
||||
namespace: "monitoring",
|
||||
configMaps: []v1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "prometheus-rules",
|
||||
Namespace: "monitoring",
|
||||
Labels: map[string]string{
|
||||
"prometheus_rule": "1",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"rules.yaml": "groups: []",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 0,
|
||||
},
|
||||
{
|
||||
name: "custom dynamically-loaded label should work",
|
||||
namespace: "default",
|
||||
configMaps: []v1.ConfigMap{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-sidecar-cm",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"k8sgpt.ai/dynamically-loaded": "true",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{
|
||||
"config": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
client := fake.NewSimpleClientset()
|
||||
|
||||
for _, cm := range tt.configMaps {
|
||||
_, err := client.CoreV1().ConfigMaps(tt.namespace).Create(context.TODO(), &cm, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
for _, pod := range tt.pods {
|
||||
_, err := client.CoreV1().Pods(tt.namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
analyzer := ConfigMapAnalyzer{}
|
||||
results, err := analyzer.Analyze(common.Analyzer{
|
||||
Client: &kubernetes.Client{Client: client},
|
||||
Context: context.TODO(),
|
||||
Namespace: tt.namespace,
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedErrors, len(results), "Expected %d errors but got %d", tt.expectedErrors, len(results))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
for _, deployment := range deployments.Items {
|
||||
var failures []common.Failure
|
||||
if *deployment.Spec.Replicas != deployment.Status.ReadyReplicas {
|
||||
if deployment.Status.Replicas > *deployment.Spec.Replicas {
|
||||
if deployment.Status.Replicas > *deployment.Spec.Replicas {
|
||||
doc := apiDoc.GetApiDocV2("spec.replicas")
|
||||
|
||||
failures = append(failures, common.Failure{
|
||||
@@ -88,7 +88,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
|
||||
Masked: util.MaskString(deployment.Name),
|
||||
},
|
||||
}})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
preAnalysis[fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name)] = common.PreAnalysis{
|
||||
|
||||
86
pkg/cache/cache_test.go
vendored
86
pkg/cache/cache_test.go
vendored
@@ -1,60 +1,60 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewReturnsExpectedCache(t *testing.T) {
|
||||
require.IsType(t, &FileBasedCache{}, New("file"))
|
||||
require.IsType(t, &AzureCache{}, New("azure"))
|
||||
require.IsType(t, &GCSCache{}, New("gcs"))
|
||||
require.IsType(t, &S3Cache{}, New("s3"))
|
||||
require.IsType(t, &InterplexCache{}, New("interplex"))
|
||||
// default fallback
|
||||
require.IsType(t, &FileBasedCache{}, New("unknown"))
|
||||
require.IsType(t, &FileBasedCache{}, New("file"))
|
||||
require.IsType(t, &AzureCache{}, New("azure"))
|
||||
require.IsType(t, &GCSCache{}, New("gcs"))
|
||||
require.IsType(t, &S3Cache{}, New("s3"))
|
||||
require.IsType(t, &InterplexCache{}, New("interplex"))
|
||||
// default fallback
|
||||
require.IsType(t, &FileBasedCache{}, New("unknown"))
|
||||
}
|
||||
|
||||
func TestNewCacheProvider_InterplexAndInvalid(t *testing.T) {
|
||||
// valid: interplex
|
||||
cp, err := NewCacheProvider("interplex", "", "", "localhost:1", "", "", "", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "interplex", cp.CurrentCacheType)
|
||||
require.Equal(t, "localhost:1", cp.Interplex.ConnectionString)
|
||||
// valid: interplex
|
||||
cp, err := NewCacheProvider("interplex", "", "", "localhost:1", "", "", "", false)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "interplex", cp.CurrentCacheType)
|
||||
require.Equal(t, "localhost:1", cp.Interplex.ConnectionString)
|
||||
|
||||
// invalid type
|
||||
_, err = NewCacheProvider("not-a-type", "", "", "", "", "", "", false)
|
||||
require.Error(t, err)
|
||||
// invalid type
|
||||
_, err = NewCacheProvider("not-a-type", "", "", "", "", "", "", false)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAddRemoveRemoteCacheAndGet(t *testing.T) {
|
||||
// isolate viper with temp config file
|
||||
tmpFile, err := os.CreateTemp("", "k8sgpt-cache-config-*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}()
|
||||
viper.Reset()
|
||||
viper.SetConfigFile(tmpFile.Name())
|
||||
// isolate viper with temp config file
|
||||
tmpFile, err := os.CreateTemp("", "k8sgpt-cache-config-*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}()
|
||||
viper.Reset()
|
||||
viper.SetConfigFile(tmpFile.Name())
|
||||
|
||||
// add interplex remote cache
|
||||
cp := CacheProvider{}
|
||||
cp.CurrentCacheType = "interplex"
|
||||
cp.Interplex.ConnectionString = "localhost:1"
|
||||
require.NoError(t, AddRemoteCache(cp))
|
||||
// add interplex remote cache
|
||||
cp := CacheProvider{}
|
||||
cp.CurrentCacheType = "interplex"
|
||||
cp.Interplex.ConnectionString = "localhost:1"
|
||||
require.NoError(t, AddRemoteCache(cp))
|
||||
|
||||
// read back via GetCacheConfiguration
|
||||
c, err := GetCacheConfiguration()
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &InterplexCache{}, c)
|
||||
// read back via GetCacheConfiguration
|
||||
c, err := GetCacheConfiguration()
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &InterplexCache{}, c)
|
||||
|
||||
// remove remote cache
|
||||
require.NoError(t, RemoveRemoteCache())
|
||||
// now default should be file-based
|
||||
c2, err := GetCacheConfiguration()
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &FileBasedCache{}, c2)
|
||||
}
|
||||
// remove remote cache
|
||||
require.NoError(t, RemoveRemoteCache())
|
||||
// now default should be file-based
|
||||
c2, err := GetCacheConfiguration()
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, &FileBasedCache{}, c2)
|
||||
}
|
||||
|
||||
112
pkg/cache/file_based_test.go
vendored
112
pkg/cache/file_based_test.go
vendored
@@ -1,77 +1,77 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// withTempCacheHome sets XDG_CACHE_HOME to a temp dir for test isolation.
|
||||
func withTempCacheHome(t *testing.T) func() {
|
||||
t.Helper()
|
||||
tmp, err := os.MkdirTemp("", "k8sgpt-cache-test-*")
|
||||
require.NoError(t, err)
|
||||
old := os.Getenv("XDG_CACHE_HOME")
|
||||
require.NoError(t, os.Setenv("XDG_CACHE_HOME", tmp))
|
||||
return func() {
|
||||
_ = os.Setenv("XDG_CACHE_HOME", old)
|
||||
_ = os.RemoveAll(tmp)
|
||||
}
|
||||
t.Helper()
|
||||
tmp, err := os.MkdirTemp("", "k8sgpt-cache-test-*")
|
||||
require.NoError(t, err)
|
||||
old := os.Getenv("XDG_CACHE_HOME")
|
||||
require.NoError(t, os.Setenv("XDG_CACHE_HOME", tmp))
|
||||
return func() {
|
||||
_ = os.Setenv("XDG_CACHE_HOME", old)
|
||||
_ = os.RemoveAll(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileBasedCache_BasicOps(t *testing.T) {
|
||||
cleanup := withTempCacheHome(t)
|
||||
defer cleanup()
|
||||
cleanup := withTempCacheHome(t)
|
||||
defer cleanup()
|
||||
|
||||
c := &FileBasedCache{}
|
||||
// Configure should be a no-op
|
||||
require.NoError(t, c.Configure(CacheProvider{}))
|
||||
require.Equal(t, "file", c.GetName())
|
||||
require.False(t, c.IsCacheDisabled())
|
||||
c.DisableCache()
|
||||
require.True(t, c.IsCacheDisabled())
|
||||
c := &FileBasedCache{}
|
||||
// Configure should be a no-op
|
||||
require.NoError(t, c.Configure(CacheProvider{}))
|
||||
require.Equal(t, "file", c.GetName())
|
||||
require.False(t, c.IsCacheDisabled())
|
||||
c.DisableCache()
|
||||
require.True(t, c.IsCacheDisabled())
|
||||
|
||||
key := "testkey"
|
||||
data := "hello"
|
||||
key := "testkey"
|
||||
data := "hello"
|
||||
|
||||
// Store
|
||||
require.NoError(t, c.Store(key, data))
|
||||
// Store
|
||||
require.NoError(t, c.Store(key, data))
|
||||
|
||||
// Exists
|
||||
require.True(t, c.Exists(key))
|
||||
// Exists
|
||||
require.True(t, c.Exists(key))
|
||||
|
||||
// Load
|
||||
got, err := c.Load(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, got)
|
||||
// Load
|
||||
got, err := c.Load(key)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, got)
|
||||
|
||||
// List should include our key file
|
||||
items, err := c.List()
|
||||
require.NoError(t, err)
|
||||
// ensure at least one item and that one matches our key
|
||||
found := false
|
||||
for _, it := range items {
|
||||
if it.Name == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, found)
|
||||
// List should include our key file
|
||||
items, err := c.List()
|
||||
require.NoError(t, err)
|
||||
// ensure at least one item and that one matches our key
|
||||
found := false
|
||||
for _, it := range items {
|
||||
if it.Name == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, found)
|
||||
|
||||
// Remove
|
||||
require.NoError(t, c.Remove(key))
|
||||
require.False(t, c.Exists(key))
|
||||
// Remove
|
||||
require.NoError(t, c.Remove(key))
|
||||
require.False(t, c.Exists(key))
|
||||
}
|
||||
|
||||
func TestFileBasedCache_PathShape(t *testing.T) {
|
||||
cleanup := withTempCacheHome(t)
|
||||
defer cleanup()
|
||||
// Verify xdg.CacheFile path shape (directory and filename)
|
||||
p, err := xdg.CacheFile(filepath.Join("k8sgpt", "abc"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "abc", filepath.Base(p))
|
||||
require.Contains(t, p, "k8sgpt")
|
||||
}
|
||||
cleanup := withTempCacheHome(t)
|
||||
defer cleanup()
|
||||
// Verify xdg.CacheFile path shape (directory and filename)
|
||||
p, err := xdg.CacheFile(filepath.Join("k8sgpt", "abc"))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "abc", filepath.Base(p))
|
||||
require.Contains(t, p, "k8sgpt")
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
package custom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
schemav1 "buf.build/gen/go/k8sgpt-ai/k8sgpt/protocolbuffers/go/schema/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// mockAnalyzerClient implements rpc.CustomAnalyzerServiceClient for testing
|
||||
type mockAnalyzerClient struct{
|
||||
resp *schemav1.RunResponse
|
||||
err error
|
||||
type mockAnalyzerClient struct {
|
||||
resp *schemav1.RunResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *mockAnalyzerClient) Run(ctx context.Context, in *schemav1.RunRequest, opts ...grpc.CallOption) (*schemav1.RunResponse, error) {
|
||||
return m.resp, m.err
|
||||
return m.resp, m.err
|
||||
}
|
||||
|
||||
func TestClientRunMapsResponse(t *testing.T) {
|
||||
// prepare fake response
|
||||
resp := &schemav1.RunResponse{
|
||||
Result: &schemav1.Result{
|
||||
Name: "AnalyzerA",
|
||||
Kind: "Pod",
|
||||
Details: "details",
|
||||
ParentObject: "Deployment/foo",
|
||||
},
|
||||
}
|
||||
cli := &Client{analyzerClient: &mockAnalyzerClient{resp: resp}}
|
||||
// prepare fake response
|
||||
resp := &schemav1.RunResponse{
|
||||
Result: &schemav1.Result{
|
||||
Name: "AnalyzerA",
|
||||
Kind: "Pod",
|
||||
Details: "details",
|
||||
ParentObject: "Deployment/foo",
|
||||
},
|
||||
}
|
||||
cli := &Client{analyzerClient: &mockAnalyzerClient{resp: resp}}
|
||||
|
||||
got, err := cli.Run()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "AnalyzerA", got.Name)
|
||||
require.Equal(t, "Pod", got.Kind)
|
||||
require.Equal(t, "details", got.Details)
|
||||
require.Equal(t, "Deployment/foo", got.ParentObject)
|
||||
require.Len(t, got.Error, 0)
|
||||
}
|
||||
got, err := cli.Run()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "AnalyzerA", got.Name)
|
||||
require.Equal(t, "Pod", got.Kind)
|
||||
require.Equal(t, "details", got.Details)
|
||||
require.Equal(t, "Deployment/foo", got.ParentObject)
|
||||
require.Len(t, got.Error, 0)
|
||||
}
|
||||
|
||||
@@ -251,10 +251,10 @@ func (s *K8sGptMCPServer) handleListNamespaces(ctx context.Context, request mcp.
|
||||
// handleListEvents lists Kubernetes events
|
||||
func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var req struct {
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
||||
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
||||
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
}
|
||||
if err := request.BindArguments(&req); err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
|
||||
@@ -307,4 +307,4 @@ func TestQuery_GetCompletionError(t *testing.T) {
|
||||
mockAI.AssertExpectations(t)
|
||||
mockFactory.AssertExpectations(t)
|
||||
mockConfig.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,10 @@ type Config struct {
|
||||
QueryHandler *query.Handler
|
||||
Logger *zap.Logger
|
||||
// Filters can be injected into the server to limit analysis to specific analyzers
|
||||
Filters []string
|
||||
metricsServer *http.Server
|
||||
listener net.Listener
|
||||
EnableHttp bool
|
||||
Filters []string
|
||||
metricsServer *http.Server
|
||||
listener net.Listener
|
||||
EnableHttp bool
|
||||
}
|
||||
|
||||
type Health struct {
|
||||
|
||||
Reference in New Issue
Block a user