Skip to content

Commit

Permalink
feat: add label selector on analyzers
Browse files Browse the repository at this point in the history
Signed-off-by: JuHyung-Son <[email protected]>
  • Loading branch information
JuHyung-Son committed Jul 16, 2024
1 parent b665914 commit fa125d7
Show file tree
Hide file tree
Showing 34 changed files with 929 additions and 22 deletions.
2 changes: 1 addition & 1 deletion pkg/analyzer/cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (analyzer CronJobAnalyzer) Analyze(a common.Analyzer) ([]common.Result, err
"analyzer_name": kind,
})

cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{})
cronJobList, err := a.Client.GetClient().BatchV1().CronJobs(a.Namespace).List(a.Context, v1.ListOptions{LabelSelector: a.LabelSelector})
if err != nil {
return nil, err
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/analyzer/cronjob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,45 @@ func TestCronJobAnalyzer(t *testing.T) {
require.Equal(t, expectations[i], result.Name)
}
}

func TestCronJobAnalyzerLabelSelectorFiltering(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: fake.NewSimpleClientset(
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "CJ1",
Namespace: "default",
Labels: map[string]string{
"app": "cronjob",
},
},
},
&batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{
Name: "CJ2",
Namespace: "default",
},
},
),
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=cronjob",
}

cjAnalyzer := CronJobAnalyzer{}
results, err := cjAnalyzer.Analyze(config)
require.NoError(t, err)
require.Equal(t, 1, len(results))
require.Equal(t, "default/CJ1", results[0].Name)
}
2 changes: 1 addition & 1 deletion pkg/analyzer/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (d DeploymentAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
"analyzer_name": kind,
})

deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{})
deployments, err := a.Client.GetClient().AppsV1().Deployments(a.Namespace).List(context.Background(), v1.ListOptions{LabelSelector: a.LabelSelector})
if err != nil {
return nil, err
}
Expand Down
52 changes: 52 additions & 0 deletions pkg/analyzer/deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,55 @@ func TestDeploymentAnalyzerNamespaceFiltering(t *testing.T) {
assert.Equal(t, analysisResults[0].Kind, "Deployment")
assert.Equal(t, analysisResults[0].Name, "default/example")
}

func TestDeploymentAnalyzerLabelSelectorFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Labels: map[string]string{
"app": "deployment",
},
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{},
},
},
},
},
&appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example2",
Namespace: "default",
},
Spec: appsv1.DeploymentSpec{
Replicas: func() *int32 { i := int32(3); return &i }(),
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{},
},
},
},
},
)

config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=deployment",
}

deploymentAnalyzer := DeploymentAnalyzer{}
analysisResults, err := deploymentAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
12 changes: 11 additions & 1 deletion pkg/analyzer/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ package analyzer

import (
"fmt"
"strings"

"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
)
Expand All @@ -41,7 +43,15 @@ func (GatewayAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
if err != nil {
return nil, err
}
if err := client.List(a.Context, gtwList, &ctrl.ListOptions{}); err != nil {
labelSelectorMap := make(map[string]string)
for _, s := range strings.Split(a.LabelSelector, ",") {
parts := strings.SplitN(s, "=", 2)
if len(parts) == 2 {
labelSelectorMap[parts[0]] = parts[1]
}
}
labelSelector := labels.SelectorFromSet(labels.Set(labelSelectorMap))
if err := client.List(a.Context, gtwList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
return nil, err
}

Expand Down
78 changes: 74 additions & 4 deletions pkg/analyzer/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ func BuildGatewayClass(name string) gtwapi.GatewayClass {
return GatewayClass
}

func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus) gtwapi.Gateway {
func BuildGateway(className gtwapi.ObjectName, status metav1.ConditionStatus, labels map[string]string) gtwapi.Gateway {
Gateway := gtwapi.Gateway{}
Gateway.Name = "foobar"
Gateway.Namespace = "default"
if labels != nil {
Gateway.Labels = labels
}
Gateway.Spec.GatewayClassName = className
Gateway.Spec.Listeners = []gtwapi.Listener{
{
Expand All @@ -53,7 +56,7 @@ func TestGatewayAnalyzer(t *testing.T) {
AcceptedStatus := metav1.ConditionTrue
GatewayClass := BuildGatewayClass(string(ClassName))

Gateway := BuildGateway(ClassName, AcceptedStatus)
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)
// Create a Gateway Analyzer instance with the fake client
scheme := scheme.Scheme

Expand Down Expand Up @@ -91,7 +94,7 @@ func TestGatewayAnalyzer(t *testing.T) {
func TestMissingClassGatewayAnalyzer(t *testing.T) {
ClassName := gtwapi.ObjectName("non-existed")
AcceptedStatus := metav1.ConditionTrue
Gateway := BuildGateway(ClassName, AcceptedStatus)
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)

// Create a Gateway Analyzer instance with the fake client
scheme := scheme.Scheme
Expand Down Expand Up @@ -130,7 +133,7 @@ func TestStatusGatewayAnalyzer(t *testing.T) {
AcceptedStatus := metav1.ConditionUnknown
GatewayClass := BuildGatewayClass(string(ClassName))

Gateway := BuildGateway(ClassName, AcceptedStatus)
Gateway := BuildGateway(ClassName, AcceptedStatus, nil)

// Create a Gateway Analyzer instance with the fake client
scheme := scheme.Scheme
Expand Down Expand Up @@ -178,3 +181,70 @@ func TestStatusGatewayAnalyzer(t *testing.T) {
t.Errorf("Expected message, <%v> , not found in Gateway's analysis results", want)
}
}

func TestGatewayAnalyzerLabelSelectorFiltering(t *testing.T) {
ClassName := gtwapi.ObjectName("non-existed")
AcceptedStatus := metav1.ConditionTrue

Gateway := BuildGateway(ClassName, AcceptedStatus, map[string]string{"app": "gateway"})
scheme := scheme.Scheme
err := gtwapi.Install(scheme)
if err != nil {
t.Error(err)
}
err = apiextensionsv1.AddToScheme(scheme)
if err != nil {
t.Error(err)
}
objects := []runtime.Object{
&Gateway,
}

fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()

analyzerInstance := GatewayAnalyzer{}
// without label selector should return 1 result
config := common.Analyzer{
Client: &kubernetes.Client{
CtrlClient: fakeClient,
},
Context: context.Background(),
Namespace: "default",
}
analysisResults, err := analyzerInstance.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)

// with label selector should return 1 result
config = common.Analyzer{
Client: &kubernetes.Client{
CtrlClient: fakeClient,
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=gateway",
}
analysisResults, err = analyzerInstance.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)

// with wrong label selector should return 0 result
config = common.Analyzer{
Client: &kubernetes.Client{
CtrlClient: fakeClient,
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=wrong",
}
analysisResults, err = analyzerInstance.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 0)

}
12 changes: 11 additions & 1 deletion pkg/analyzer/gatewayclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ package analyzer

import (
"fmt"
"strings"

"github.com/k8sgpt-ai/k8sgpt/pkg/common"
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
gtwapi "sigs.k8s.io/gateway-api/apis/v1"
)
Expand All @@ -39,7 +41,15 @@ func (GatewayClassAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error)
if err != nil {
return nil, err
}
if err := client.List(a.Context, gcList, &ctrl.ListOptions{}); err != nil {
labelSelectorMap := make(map[string]string)
for _, s := range strings.Split(a.LabelSelector, ",") {
parts := strings.SplitN(s, "=", 2)
if len(parts) == 2 {
labelSelectorMap[parts[0]] = parts[1]
}
}
labelSelector := labels.SelectorFromSet(labels.Set(labelSelectorMap))
if err := client.List(a.Context, gcList, &ctrl.ListOptions{LabelSelector: labelSelector}); err != nil {
return nil, err
}
var preAnalysis = map[string]common.PreAnalysis{}
Expand Down
48 changes: 48 additions & 0 deletions pkg/analyzer/gatewayclass_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,51 @@ func TestGatewayClassAnalyzer(t *testing.T) {
assert.Equal(t, len(analysisResults), 1)

}

func TestGatewayClassAnalyzerLabelSelectorFiltering(t *testing.T) {
condition := metav1.Condition{
Type: "Accepted",
Status: "Ready",
Message: "Ready",
Reason: "Ready",
}

// Create two GatewayClasses with different labels
GatewayClass := &gtwapi.GatewayClass{}
GatewayClass.Name = "foobar"
GatewayClass.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
GatewayClass.Labels = map[string]string{"app": "gatewayclass"}
GatewayClass.Status.Conditions = []metav1.Condition{condition}

GatewayClass2 := &gtwapi.GatewayClass{}
GatewayClass2.Name = "foobar2"
GatewayClass2.Spec.ControllerName = "gateway.fooproxy.io/gatewayclass-controller"
GatewayClass2.Status.Conditions = []metav1.Condition{condition}

scheme := scheme.Scheme
err := gtwapi.Install(scheme)
if err != nil {
t.Error(err)
}
err = apiextensionsv1.AddToScheme(scheme)
if err != nil {
t.Error(err)
}

fakeClient := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(GatewayClass, GatewayClass2).Build()

analyzerInstance := GatewayClassAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
CtrlClient: fakeClient,
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=gatewayclass",
}
analysisResults, err := analyzerInstance.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
2 changes: 1 addition & 1 deletion pkg/analyzer/hpa.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (HpaAnalyzer) Analyze(a common.Analyzer) ([]common.Result, error) {
"analyzer_name": kind,
})

list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{})
list, err := a.Client.GetClient().AutoscalingV1().HorizontalPodAutoscalers(a.Namespace).List(a.Context, metav1.ListOptions{LabelSelector: a.LabelSelector})
if err != nil {
return nil, err
}
Expand Down
34 changes: 34 additions & 0 deletions pkg/analyzer/hpaAnalyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,37 @@ func TestHPAAnalyzerNamespaceFiltering(t *testing.T) {
}
assert.Equal(t, len(analysisResults), 1)
}

func TestHPAAnalyzerLabelSelectorFiltering(t *testing.T) {
clientset := fake.NewSimpleClientset(
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example",
Namespace: "default",
Labels: map[string]string{
"app": "hpa",
},
},
},
&autoscalingv1.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: "example2",
Namespace: "default",
},
},
)
hpaAnalyzer := HpaAnalyzer{}
config := common.Analyzer{
Client: &kubernetes.Client{
Client: clientset,
},
Context: context.Background(),
Namespace: "default",
LabelSelector: "app=hpa",
}
analysisResults, err := hpaAnalyzer.Analyze(config)
if err != nil {
t.Error(err)
}
assert.Equal(t, len(analysisResults), 1)
}
Loading

0 comments on commit fa125d7

Please sign in to comment.