diff --git a/pkg/provider/kubernetes/deployment_events.go b/pkg/provider/kubernetes/deployment_events.go index e6eb7de..36787b7 100644 --- a/pkg/provider/kubernetes/deployment_events.go +++ b/pkg/provider/kubernetes/deployment_events.go @@ -18,6 +18,10 @@ func (p *KubernetesProvider) watchDeployents(instance chan<- string) cache.Share return } + if *oldDeployment.Spec.Replicas == 0 { + return + } + if *newDeployment.Spec.Replicas == 0 { parsed := DeploymentName(newDeployment, ParseOptions{Delimiter: p.delimiter}) instance <- parsed.Original diff --git a/pkg/provider/kubernetes/deployment_list.go b/pkg/provider/kubernetes/deployment_list.go index f5fc2f8..27b1c51 100644 --- a/pkg/provider/kubernetes/deployment_list.go +++ b/pkg/provider/kubernetes/deployment_list.go @@ -4,18 +4,20 @@ import ( "context" "github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/types" - "github.com/sablierapp/sablier/pkg/provider" v1 "k8s.io/api/apps/v1" core_v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strings" ) -func (p *KubernetesProvider) DeploymentList(ctx context.Context, options provider.InstanceListOptions) ([]types.Instance, error) { +func (p *KubernetesProvider) DeploymentList(ctx context.Context) ([]types.Instance, error) { + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + discovery.LabelEnable: "true", + }, + } deployments, err := p.Client.AppsV1().Deployments(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ - LabelSelector: strings.Join(options.Labels, ","), + LabelSelector: metav1.FormatLabelSelector(&labelSelector), }) - if err != nil { return nil, err } @@ -47,3 +49,33 @@ func (p *KubernetesProvider) deploymentToInstance(d *v1.Deployment) types.Instan Group: group, } } + +func (p *KubernetesProvider) DeploymentGroups(ctx context.Context) (map[string][]string, error) { + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + discovery.LabelEnable: "true", + }, + } + deployments, err := p.Client.AppsV1().Deployments(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ + LabelSelector: metav1.FormatLabelSelector(&labelSelector), + }) + + if err != nil { + return nil, err + } + + groups := make(map[string][]string) + for _, deployment := range deployments.Items { + groupName := deployment.Labels[discovery.LabelGroup] + if len(groupName) == 0 { + groupName = discovery.LabelGroupDefaultValue + } + + group := groups[groupName] + parsed := DeploymentName(&deployment, ParseOptions{Delimiter: p.delimiter}) + group = append(group, parsed.Original) + groups[groupName] = group + } + + return groups, nil +} diff --git a/pkg/provider/kubernetes/instance_events_test.go b/pkg/provider/kubernetes/instance_events_test.go new file mode 100644 index 0000000..1485add --- /dev/null +++ b/pkg/provider/kubernetes/instance_events_test.go @@ -0,0 +1,95 @@ +package kubernetes_test + +import ( + "context" + "github.com/neilotoole/slogt" + "github.com/sablierapp/sablier/config" + "github.com/sablierapp/sablier/pkg/provider/kubernetes" + "gotest.tools/v3/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" + "time" +) + +func TestKubernetesProvider_NotifyInstanceStopped(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + kind := setupKinD(t, ctx) + p, err := kubernetes.NewKubernetesProvider(ctx, kind.client, slogt.New(t), config.NewProviderConfig().Kubernetes) + assert.NilError(t, err) + + waitC := make(chan string) + go p.NotifyInstanceStopped(ctx, waitC) + + t.Run("deployment is scaled to 0 replicas", func(t *testing.T) { + d, err := kind.CreateMimicDeployment(ctx, MimicOptions{}) + assert.NilError(t, err) + + err = WaitForDeploymentReady(ctx, kind.client, d.Namespace, d.Name) + assert.NilError(t, err) + + s, err := p.Client.AppsV1().Deployments(d.Namespace).GetScale(ctx, d.Name, metav1.GetOptions{}) + assert.NilError(t, err) + + s.Spec.Replicas = 0 + _, err = p.Client.AppsV1().Deployments(d.Namespace).UpdateScale(ctx, d.Name, s, metav1.UpdateOptions{}) + + name := <-waitC + + // Docker container name is prefixed with a slash, but we don't use it + assert.Equal(t, name, kubernetes.DeploymentName(d, kubernetes.ParseOptions{Delimiter: "_"}).Original) + }) + t.Run("deployment is removed", func(t *testing.T) { + d, err := kind.CreateMimicDeployment(ctx, MimicOptions{}) + assert.NilError(t, err) + + err = WaitForDeploymentReady(ctx, kind.client, d.Namespace, d.Name) + assert.NilError(t, err) + + err = p.Client.AppsV1().Deployments(d.Namespace).Delete(ctx, d.Name, metav1.DeleteOptions{}) + assert.NilError(t, err) + + name := <-waitC + + // Docker container name is prefixed with a slash, but we don't use it + assert.Equal(t, name, kubernetes.DeploymentName(d, kubernetes.ParseOptions{Delimiter: "_"}).Original) + }) + t.Run("statefulSet is scaled to 0 replicas", func(t *testing.T) { + ss, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{}) + assert.NilError(t, err) + + err = WaitForStatefulSetReady(ctx, kind.client, ss.Namespace, ss.Name) + assert.NilError(t, err) + + s, err := p.Client.AppsV1().StatefulSets(ss.Namespace).GetScale(ctx, ss.Name, metav1.GetOptions{}) + assert.NilError(t, err) + + s.Spec.Replicas = 0 + _, err = p.Client.AppsV1().StatefulSets(ss.Namespace).UpdateScale(ctx, ss.Name, s, metav1.UpdateOptions{}) + + name := <-waitC + + // Docker container name is prefixed with a slash, but we don't use it + assert.Equal(t, name, kubernetes.StatefulSetName(ss, kubernetes.ParseOptions{Delimiter: "_"}).Original) + }) + + t.Run("statefulSet is removed", func(t *testing.T) { + ss, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{}) + assert.NilError(t, err) + + err = WaitForStatefulSetReady(ctx, kind.client, ss.Namespace, ss.Name) + assert.NilError(t, err) + + err = p.Client.AppsV1().StatefulSets(ss.Namespace).Delete(ctx, ss.Name, metav1.DeleteOptions{}) + assert.NilError(t, err) + + name := <-waitC + + // Docker container name is prefixed with a slash, but we don't use it + assert.Equal(t, name, kubernetes.StatefulSetName(ss, kubernetes.ParseOptions{Delimiter: "_"}).Original) + }) +} diff --git a/pkg/provider/kubernetes/instance_list.go b/pkg/provider/kubernetes/instance_list.go index f4eb9f6..92f5e54 100644 --- a/pkg/provider/kubernetes/instance_list.go +++ b/pkg/provider/kubernetes/instance_list.go @@ -7,15 +7,38 @@ import ( ) func (p *KubernetesProvider) InstanceList(ctx context.Context, options provider.InstanceListOptions) ([]types.Instance, error) { - deployments, err := p.DeploymentList(ctx, options) + deployments, err := p.DeploymentList(ctx) if err != nil { return nil, err } - statefulSets, err := p.StatefulSetList(ctx, options) + statefulSets, err := p.StatefulSetList(ctx) if err != nil { return nil, err } return append(deployments, statefulSets...), nil } + +func (p *KubernetesProvider) InstanceGroups(ctx context.Context) (map[string][]string, error) { + deployments, err := p.DeploymentGroups(ctx) + if err != nil { + return nil, err + } + + statefulSets, err := p.StatefulSetGroups(ctx) + if err != nil { + return nil, err + } + + groups := make(map[string][]string) + for group, instances := range deployments { + groups[group] = instances + } + + for group, instances := range statefulSets { + groups[group] = append(groups[group], instances...) + } + + return groups, nil +} diff --git a/pkg/provider/kubernetes/instance_list_test.go b/pkg/provider/kubernetes/instance_list_test.go new file mode 100644 index 0000000..02d660c --- /dev/null +++ b/pkg/provider/kubernetes/instance_list_test.go @@ -0,0 +1,145 @@ +package kubernetes_test + +import ( + "github.com/neilotoole/slogt" + "github.com/sablierapp/sablier/app/types" + "github.com/sablierapp/sablier/config" + "github.com/sablierapp/sablier/pkg/provider" + "github.com/sablierapp/sablier/pkg/provider/kubernetes" + "gotest.tools/v3/assert" + "sort" + "strings" + "testing" +) + +func TestKubernetesProvider_InstanceList(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + ctx := t.Context() + kind := setupKinD(t, ctx) + p, err := kubernetes.NewKubernetesProvider(ctx, kind.client, slogt.New(t), config.NewProviderConfig().Kubernetes) + assert.NilError(t, err) + + d1, err := kind.CreateMimicDeployment(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + }, + }) + assert.NilError(t, err) + + d2, err := kind.CreateMimicDeployment(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + "sablier.group": "my-group", + }, + }) + assert.NilError(t, err) + + ss1, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + }, + }) + assert.NilError(t, err) + + ss2, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + "sablier.group": "my-group", + }, + }) + assert.NilError(t, err) + + got, err := p.InstanceList(ctx, provider.InstanceListOptions{ + All: true, + }) + assert.NilError(t, err) + + want := []types.Instance{ + { + Name: kubernetes.DeploymentName(d1, kubernetes.ParseOptions{Delimiter: "_"}).Original, + Group: "default", + }, + { + Name: kubernetes.DeploymentName(d2, kubernetes.ParseOptions{Delimiter: "_"}).Original, + Group: "my-group", + }, + { + Name: kubernetes.StatefulSetName(ss1, kubernetes.ParseOptions{Delimiter: "_"}).Original, + Group: "default", + }, + { + Name: kubernetes.StatefulSetName(ss2, kubernetes.ParseOptions{Delimiter: "_"}).Original, + Group: "my-group", + }, + } + // Assert go is equal to want + // Sort both array to ensure they are equal + sort.Slice(got, func(i, j int) bool { + return strings.Compare(got[i].Name, got[j].Name) < 0 + }) + sort.Slice(want, func(i, j int) bool { + return strings.Compare(want[i].Name, want[j].Name) < 0 + }) + assert.DeepEqual(t, got, want) +} + +func TestKubernetesProvider_InstanceGroups(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + ctx := t.Context() + kind := setupKinD(t, ctx) + p, err := kubernetes.NewKubernetesProvider(ctx, kind.client, slogt.New(t), config.NewProviderConfig().Kubernetes) + assert.NilError(t, err) + + d1, err := kind.CreateMimicDeployment(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + }, + }) + assert.NilError(t, err) + + d2, err := kind.CreateMimicDeployment(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + "sablier.group": "my-group-1", + }, + }) + assert.NilError(t, err) + + ss1, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + }, + }) + assert.NilError(t, err) + + ss2, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{ + Labels: map[string]string{ + "sablier.enable": "true", + "sablier.group": "my-group-2", + }, + }) + assert.NilError(t, err) + + got, err := p.InstanceGroups(ctx) + assert.NilError(t, err) + + want := map[string][]string{ + "default": { + kubernetes.DeploymentName(d1, kubernetes.ParseOptions{Delimiter: "_"}).Original, + kubernetes.StatefulSetName(ss1, kubernetes.ParseOptions{Delimiter: "_"}).Original, + }, + "my-group-1": { + kubernetes.DeploymentName(d2, kubernetes.ParseOptions{Delimiter: "_"}).Original, + }, + "my-group-2": { + kubernetes.StatefulSetName(ss2, kubernetes.ParseOptions{Delimiter: "_"}).Original, + }, + } + assert.DeepEqual(t, got, want) +} diff --git a/pkg/provider/kubernetes/instance_start.go b/pkg/provider/kubernetes/instance_start.go new file mode 100644 index 0000000..a621505 --- /dev/null +++ b/pkg/provider/kubernetes/instance_start.go @@ -0,0 +1,12 @@ +package kubernetes + +import "context" + +func (p *KubernetesProvider) InstanceStart(ctx context.Context, name string) error { + parsed, err := ParseName(name, ParseOptions{Delimiter: p.delimiter}) + if err != nil { + return err + } + + return p.scale(ctx, parsed, parsed.Replicas) +} diff --git a/pkg/provider/kubernetes/instance_start_test.go b/pkg/provider/kubernetes/instance_start_test.go new file mode 100644 index 0000000..69f148e --- /dev/null +++ b/pkg/provider/kubernetes/instance_start_test.go @@ -0,0 +1,114 @@ +package kubernetes_test + +import ( + "context" + "fmt" + "github.com/neilotoole/slogt" + "github.com/sablierapp/sablier/config" + "github.com/sablierapp/sablier/pkg/provider/kubernetes" + "gotest.tools/v3/assert" + "testing" +) + +func TestKubernetesProvider_InstanceStart(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + ctx := context.Background() + type args struct { + do func(kind *kindContainer) (string, error) + } + tests := []struct { + name string + args args + err error + }{ + { + name: "invalid name", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "invalid-name", nil + }, + }, + err: fmt.Errorf("invalid name [invalid-name] should be: kind_namespace_name_replicas"), + }, + { + name: "non existing deployment start", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "deployment_default_my-deployment_1", nil + }, + }, + err: fmt.Errorf("deployments/scale.apps \"my-deployment\" not found"), + }, + { + name: "deployment start as expected", + args: args{ + do: func(kind *kindContainer) (string, error) { + d, err := kind.CreateMimicDeployment(ctx, MimicOptions{}) + if err != nil { + return "", err + } + + if err = WaitForDeploymentReady(ctx, kind.client, d.Namespace, d.Name); err != nil { + return "", fmt.Errorf("error waiting for deployment: %w", err) + } + + return kubernetes.DeploymentName(d, kubernetes.ParseOptions{Delimiter: "_"}).Original, nil + }, + }, + err: nil, + }, + { + name: "non existing statefulSet start", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "statefulset_default_my-statefulset_1", nil + }, + }, + err: fmt.Errorf("statefulsets.apps \"my-statefulset\" not found"), + }, + { + name: "statefulSet start as expected", + args: args{ + do: func(kind *kindContainer) (string, error) { + ss, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{}) + if err != nil { + return "", err + } + + if err = WaitForStatefulSetReady(ctx, kind.client, ss.Namespace, ss.Name); err != nil { + return "", fmt.Errorf("error waiting for statefulSet: %w", err) + } + + return kubernetes.StatefulSetName(ss, kubernetes.ParseOptions{Delimiter: "_"}).Original, nil + }, + }, + err: nil, + }, + } + kind := setupKinD(t, ctx) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + p, err := kubernetes.NewKubernetesProvider(ctx, kind.client, slogt.New(t), config.NewProviderConfig().Kubernetes) + assert.NilError(t, err) + + name, err := tt.args.do(kind) + assert.NilError(t, err) + + err = p.InstanceStart(t.Context(), name) + if tt.err != nil { + assert.Error(t, err, tt.err.Error()) + } else { + assert.NilError(t, err) + + status, err := p.InstanceInspect(t.Context(), name) + assert.NilError(t, err) + + assert.Equal(t, status.IsReady(), true) + } + }) + } +} diff --git a/pkg/provider/kubernetes/instance_stop.go b/pkg/provider/kubernetes/instance_stop.go new file mode 100644 index 0000000..b858d83 --- /dev/null +++ b/pkg/provider/kubernetes/instance_stop.go @@ -0,0 +1,12 @@ +package kubernetes + +import "context" + +func (p *KubernetesProvider) InstanceStop(ctx context.Context, name string) error { + parsed, err := ParseName(name, ParseOptions{Delimiter: p.delimiter}) + if err != nil { + return err + } + + return p.scale(ctx, parsed, 0) +} diff --git a/pkg/provider/kubernetes/instance_stop_test.go b/pkg/provider/kubernetes/instance_stop_test.go new file mode 100644 index 0000000..d0e458f --- /dev/null +++ b/pkg/provider/kubernetes/instance_stop_test.go @@ -0,0 +1,114 @@ +package kubernetes_test + +import ( + "context" + "fmt" + "github.com/neilotoole/slogt" + "github.com/sablierapp/sablier/config" + "github.com/sablierapp/sablier/pkg/provider/kubernetes" + "gotest.tools/v3/assert" + "testing" +) + +func TestKubernetesProvider_InstanceStop(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + ctx := context.Background() + type args struct { + do func(kind *kindContainer) (string, error) + } + tests := []struct { + name string + args args + err error + }{ + { + name: "invalid name", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "invalid-name", nil + }, + }, + err: fmt.Errorf("invalid name [invalid-name] should be: kind_namespace_name_replicas"), + }, + { + name: "non existing deployment stop", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "deployment_default_my-deployment_1", nil + }, + }, + err: fmt.Errorf("deployments/scale.apps \"my-deployment\" not found"), + }, + { + name: "deployment stop as expected", + args: args{ + do: func(kind *kindContainer) (string, error) { + d, err := kind.CreateMimicDeployment(ctx, MimicOptions{}) + if err != nil { + return "", err + } + + if err = WaitForDeploymentReady(ctx, kind.client, d.Namespace, d.Name); err != nil { + return "", fmt.Errorf("error waiting for deployment: %w", err) + } + + return kubernetes.DeploymentName(d, kubernetes.ParseOptions{Delimiter: "_"}).Original, nil + }, + }, + err: nil, + }, + { + name: "non existing statefulSet stop", + args: args{ + do: func(kind *kindContainer) (string, error) { + return "statefulset_default_my-statefulset_1", nil + }, + }, + err: fmt.Errorf("statefulsets.apps \"my-statefulset\" not found"), + }, + { + name: "statefulSet stop as expected", + args: args{ + do: func(kind *kindContainer) (string, error) { + ss, err := kind.CreateMimicStatefulSet(ctx, MimicOptions{}) + if err != nil { + return "", err + } + + if err = WaitForStatefulSetReady(ctx, kind.client, ss.Namespace, ss.Name); err != nil { + return "", fmt.Errorf("error waiting for statefulSet: %w", err) + } + + return kubernetes.StatefulSetName(ss, kubernetes.ParseOptions{Delimiter: "_"}).Original, nil + }, + }, + err: nil, + }, + } + kind := setupKinD(t, ctx) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + p, err := kubernetes.NewKubernetesProvider(ctx, kind.client, slogt.New(t), config.NewProviderConfig().Kubernetes) + assert.NilError(t, err) + + name, err := tt.args.do(kind) + assert.NilError(t, err) + + err = p.InstanceStop(t.Context(), name) + if tt.err != nil { + assert.Error(t, err, tt.err.Error()) + } else { + assert.NilError(t, err) + + status, err := p.InstanceInspect(t.Context(), name) + assert.NilError(t, err) + + assert.Equal(t, status.IsReady(), false) + } + }) + } +} diff --git a/pkg/provider/kubernetes/kubernetes.go b/pkg/provider/kubernetes/kubernetes.go index c95949f..d6284ce 100644 --- a/pkg/provider/kubernetes/kubernetes.go +++ b/pkg/provider/kubernetes/kubernetes.go @@ -2,13 +2,10 @@ package kubernetes import ( "context" - "github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/pkg/provider" - core_v1 "k8s.io/api/core/v1" "log/slog" providerConfig "github.com/sablierapp/sablier/config" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) @@ -42,66 +39,3 @@ func NewKubernetesProvider(ctx context.Context, client *kubernetes.Clientset, lo }, nil } - -func (p *KubernetesProvider) InstanceStart(ctx context.Context, name string) error { - parsed, err := ParseName(name, ParseOptions{Delimiter: p.delimiter}) - if err != nil { - return err - } - - return p.scale(ctx, parsed, parsed.Replicas) -} - -func (p *KubernetesProvider) InstanceStop(ctx context.Context, name string) error { - parsed, err := ParseName(name, ParseOptions{Delimiter: p.delimiter}) - if err != nil { - return err - } - - return p.scale(ctx, parsed, 0) -} - -func (p *KubernetesProvider) InstanceGroups(ctx context.Context) (map[string][]string, error) { - deployments, err := p.Client.AppsV1().Deployments(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ - LabelSelector: discovery.LabelEnable, - }) - - if err != nil { - return nil, err - } - - groups := make(map[string][]string) - for _, deployment := range deployments.Items { - groupName := deployment.Labels[discovery.LabelGroup] - if len(groupName) == 0 { - groupName = discovery.LabelGroupDefaultValue - } - - group := groups[groupName] - parsed := DeploymentName(&deployment, ParseOptions{Delimiter: p.delimiter}) - group = append(group, parsed.Original) - groups[groupName] = group - } - - statefulSets, err := p.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ - LabelSelector: discovery.LabelEnable, - }) - - if err != nil { - return nil, err - } - - for _, statefulSet := range statefulSets.Items { - groupName := statefulSet.Labels[discovery.LabelGroup] - if len(groupName) == 0 { - groupName = discovery.LabelGroupDefaultValue - } - - group := groups[groupName] - parsed := StatefulSetName(&statefulSet, ParseOptions{Delimiter: p.delimiter}) - group = append(group, parsed.Original) - groups[groupName] = group - } - - return groups, nil -} diff --git a/pkg/provider/kubernetes/statefulset_events.go b/pkg/provider/kubernetes/statefulset_events.go index 57e931d..050f750 100644 --- a/pkg/provider/kubernetes/statefulset_events.go +++ b/pkg/provider/kubernetes/statefulset_events.go @@ -18,6 +18,10 @@ func (p *KubernetesProvider) watchStatefulSets(instance chan<- string) cache.Sha return } + if *oldStatefulSet.Spec.Replicas == 0 { + return + } + if *newStatefulSet.Spec.Replicas == 0 { parsed := StatefulSetName(newStatefulSet, ParseOptions{Delimiter: p.delimiter}) instance <- parsed.Original diff --git a/pkg/provider/kubernetes/statefulset_list.go b/pkg/provider/kubernetes/statefulset_list.go index c607717..f1df09d 100644 --- a/pkg/provider/kubernetes/statefulset_list.go +++ b/pkg/provider/kubernetes/statefulset_list.go @@ -4,18 +4,20 @@ import ( "context" "github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/types" - "github.com/sablierapp/sablier/pkg/provider" v1 "k8s.io/api/apps/v1" core_v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "strings" ) -func (p *KubernetesProvider) StatefulSetList(ctx context.Context, options provider.InstanceListOptions) ([]types.Instance, error) { +func (p *KubernetesProvider) StatefulSetList(ctx context.Context) ([]types.Instance, error) { + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + discovery.LabelEnable: "true", + }, + } statefulSets, err := p.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ - LabelSelector: strings.Join(options.Labels, ","), + LabelSelector: metav1.FormatLabelSelector(&labelSelector), }) - if err != nil { return nil, err } @@ -47,3 +49,32 @@ func (p *KubernetesProvider) statefulSetToInstance(ss *v1.StatefulSet) types.Ins Group: group, } } + +func (p *KubernetesProvider) StatefulSetGroups(ctx context.Context) (map[string][]string, error) { + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + discovery.LabelEnable: "true", + }, + } + statefulSets, err := p.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ + LabelSelector: metav1.FormatLabelSelector(&labelSelector), + }) + if err != nil { + return nil, err + } + + groups := make(map[string][]string) + for _, ss := range statefulSets.Items { + groupName := ss.Labels[discovery.LabelGroup] + if len(groupName) == 0 { + groupName = discovery.LabelGroupDefaultValue + } + + group := groups[groupName] + parsed := StatefulSetName(&ss, ParseOptions{Delimiter: p.delimiter}) + group = append(group, parsed.Original) + groups[groupName] = group + } + + return groups, nil +} diff --git a/pkg/provider/kubernetes/statefulset_list_test.go b/pkg/provider/kubernetes/statefulset_list_test.go deleted file mode 100644 index bdeb4e5..0000000 --- a/pkg/provider/kubernetes/statefulset_list_test.go +++ /dev/null @@ -1 +0,0 @@ -package kubernetes_test diff --git a/pkg/provider/kubernetes/testcontainers_test.go b/pkg/provider/kubernetes/testcontainers_test.go index edddb21..d554eaa 100644 --- a/pkg/provider/kubernetes/testcontainers_test.go +++ b/pkg/provider/kubernetes/testcontainers_test.go @@ -42,12 +42,12 @@ func (d *kindContainer) CreateMimicDeployment(ctx context.Context, opts MimicOpt if opts.Labels == nil { opts.Labels = make(map[string]string) } - opts.Labels["app"] = name - d.t.Log("Creating mimic deployment with options", opts) + d.t.Log("Creating mimic deployment with options", name, opts) replicas := int32(1) return d.client.AppsV1().Deployments("default").Create(ctx, &v1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: name, + Labels: opts.Labels, }, Spec: v1.DeploymentSpec{ Replicas: &replicas, @@ -68,7 +68,9 @@ func (d *kindContainer) CreateMimicDeployment(ctx context.Context, opts MimicOpt }, }, ObjectMeta: metav1.ObjectMeta{ - Labels: opts.Labels, + Labels: map[string]string{ + "app": name, + }, }, }, }, @@ -85,12 +87,12 @@ func (d *kindContainer) CreateMimicStatefulSet(ctx context.Context, opts MimicOp if opts.Labels == nil { opts.Labels = make(map[string]string) } - opts.Labels["app"] = name - d.t.Log("Creating mimic deployment with options", opts) + d.t.Log("Creating mimic deployment with options", name, opts) replicas := int32(1) return d.client.AppsV1().StatefulSets("default").Create(ctx, &v1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: name, + Labels: opts.Labels, }, Spec: v1.StatefulSetSpec{ Replicas: &replicas, @@ -111,7 +113,9 @@ func (d *kindContainer) CreateMimicStatefulSet(ctx context.Context, opts MimicOp }, }, ObjectMeta: metav1.ObjectMeta{ - Labels: opts.Labels, + Labels: map[string]string{ + "app": name, + }, }, }, }, @@ -121,7 +125,7 @@ func (d *kindContainer) CreateMimicStatefulSet(ctx context.Context, opts MimicOp func setupKinD(t *testing.T, ctx context.Context) *kindContainer { t.Helper() - kind, err := k3s.Run(ctx, "rancher/k3s:v1.27.1-k3s1") + kind, err := k3s.Run(ctx, "rancher/k3s:v1.32.2-k3s1") testcontainers.CleanupContainer(t, kind) assert.NilError(t, err) @@ -162,7 +166,7 @@ func generateRandomName() string { } func WaitForDeploymentReady(ctx context.Context, client kubernetes.Interface, namespace, name string) error { - ticker := time.NewTicker(2 * time.Second) + ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { @@ -183,7 +187,7 @@ func WaitForDeploymentReady(ctx context.Context, client kubernetes.Interface, na } func WaitForStatefulSetReady(ctx context.Context, client kubernetes.Interface, namespace, name string) error { - ticker := time.NewTicker(2 * time.Second) + ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { @@ -204,7 +208,7 @@ func WaitForStatefulSetReady(ctx context.Context, client kubernetes.Interface, n } func WaitForDeploymentScale(ctx context.Context, client kubernetes.Interface, namespace, name string, replicas int32) error { - ticker := time.NewTicker(2 * time.Second) + ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for { @@ -225,7 +229,7 @@ func WaitForDeploymentScale(ctx context.Context, client kubernetes.Interface, na } func WaitForStatefulSetScale(ctx context.Context, client kubernetes.Interface, namespace, name string, replicas int32) error { - ticker := time.NewTicker(2 * time.Second) + ticker := time.NewTicker(100 * time.Millisecond) defer ticker.Stop() for {