fix(kubernetes): support public labels

closes #449
This commit is contained in:
Alexis Couvreur
2024-11-16 23:13:14 -05:00
parent 2976392078
commit ad5d66e614
10 changed files with 83 additions and 74 deletions

View File

@@ -15,16 +15,13 @@ import (
func StopAllUnregisteredInstances(ctx context.Context, provider providers.Provider, registered []string) error { func StopAllUnregisteredInstances(ctx context.Context, provider providers.Provider, registered []string) error {
log.Info("Stopping all unregistered running instances") log.Info("Stopping all unregistered running instances")
log.Tracef("Retrieving all instances with label [%v=true]", LabelEnable) log.Trace("Retrieving all registered instances")
instances, err := provider.InstanceList(ctx, providers.InstanceListOptions{ instances, err := provider.List(ctx)
All: false, // Only running containers
Labels: []string{LabelEnable},
})
if err != nil { if err != nil {
return err return err
} }
log.Tracef("Found %v instances with label [%v=true]", len(instances), LabelEnable) log.Tracef("Found %v instances", len(instances))
names := make([]string, 0, len(instances)) names := make([]string, 0, len(instances))
for _, instance := range instances { for _, instance := range instances {
names = append(names, instance.Name) names = append(names, instance.Name)

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/discovery"
"github.com/sablierapp/sablier/app/providers"
"github.com/sablierapp/sablier/app/providers/mock" "github.com/sablierapp/sablier/app/providers/mock"
"github.com/sablierapp/sablier/app/types" "github.com/sablierapp/sablier/app/types"
"testing" "testing"
@@ -22,11 +21,8 @@ func TestStopAllUnregisteredInstances(t *testing.T) {
} }
registered := []string{"instance1"} registered := []string{"instance1"}
// Set up expectations for InstanceList // Set up expectations for List
mockProvider.On("InstanceList", ctx, providers.InstanceListOptions{ mockProvider.On("List", ctx).Return(instances, nil)
All: false,
Labels: []string{discovery.LabelEnable},
}).Return(instances, nil)
// Set up expectations for Stop // Set up expectations for Stop
mockProvider.On("Stop", ctx, "instance2").Return(nil) mockProvider.On("Stop", ctx, "instance2").Return(nil)
@@ -54,11 +50,8 @@ func TestStopAllUnregisteredInstances_WithError(t *testing.T) {
} }
registered := []string{"instance1"} registered := []string{"instance1"}
// Set up expectations for InstanceList // Set up expectations for List
mockProvider.On("InstanceList", ctx, providers.InstanceListOptions{ mockProvider.On("List", ctx).Return(instances, nil)
All: false,
Labels: []string{discovery.LabelEnable},
}).Return(instances, nil)
// Set up expectations for Stop with error // Set up expectations for Stop with error
mockProvider.On("Stop", ctx, "instance2").Return(errors.New("stop error")) mockProvider.On("Stop", ctx, "instance2").Return(errors.New("stop error"))

View File

@@ -7,20 +7,16 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/discovery"
"github.com/sablierapp/sablier/app/providers"
"github.com/sablierapp/sablier/app/types" "github.com/sablierapp/sablier/app/types"
"strings" "strings"
) )
func (provider *DockerClassicProvider) InstanceList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { func (provider *DockerClassicProvider) List(ctx context.Context) ([]types.Instance, error) {
args := filters.NewArgs() args := filters.NewArgs()
for _, label := range options.Labels { args.Add("label", fmt.Sprintf("%s=true", discovery.LabelEnable))
args.Add("label", label)
args.Add("label", fmt.Sprintf("%s=true", label))
}
containers, err := provider.Client.ContainerList(ctx, container.ListOptions{ containers, err := provider.Client.ContainerList(ctx, container.ListOptions{
All: options.All, All: true,
Filters: args, Filters: args,
}) })

View File

@@ -7,18 +7,14 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/discovery"
"github.com/sablierapp/sablier/app/providers"
"github.com/sablierapp/sablier/app/types" "github.com/sablierapp/sablier/app/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"strconv" "strconv"
) )
func (provider *DockerSwarmProvider) InstanceList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { func (provider *DockerSwarmProvider) List(ctx context.Context) ([]types.Instance, error) {
args := filters.NewArgs() args := filters.NewArgs()
for _, label := range options.Labels { args.Add("label", fmt.Sprintf("%s=true", discovery.LabelEnable))
args.Add("label", label)
args.Add("label", fmt.Sprintf("%s=true", label))
}
services, err := provider.Client.ServiceList(ctx, dockertypes.ServiceListOptions{ services, err := provider.Client.ServiceList(ctx, dockertypes.ServiceListOptions{
Filters: args, Filters: args,

View File

@@ -3,23 +3,31 @@ package kubernetes
import ( import (
"context" "context"
"github.com/sablierapp/sablier/app/discovery" "github.com/sablierapp/sablier/app/discovery"
"github.com/sablierapp/sablier/app/providers"
"github.com/sablierapp/sablier/app/types" "github.com/sablierapp/sablier/app/types"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
v1 "k8s.io/api/apps/v1" v1 "k8s.io/api/apps/v1"
core_v1 "k8s.io/api/core/v1" core_v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"strconv" "strconv"
"strings"
) )
func (provider *KubernetesProvider) InstanceList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { const (
deployments, err := provider.deploymentList(ctx, options) LabelEnable = "sablierapp.dev/enable"
LabelGroup = "sablierapp.dev/group"
LabelGroupDefaultValue = "default"
LabelReplicas = "sablierapp.dev/replicas"
LabelReplicasDefaultValue uint64 = 1
)
func (provider *KubernetesProvider) List(ctx context.Context) ([]types.Instance, error) {
deployments, err := provider.deploymentList(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
statefulSets, err := provider.statefulSetList(ctx, options) statefulSets, err := provider.statefulSetList(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -27,9 +35,19 @@ func (provider *KubernetesProvider) InstanceList(ctx context.Context, options pr
return append(deployments, statefulSets...), nil return append(deployments, statefulSets...), nil
} }
func (provider *KubernetesProvider) deploymentList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { func (provider *KubernetesProvider) deploymentList(ctx context.Context) ([]types.Instance, error) {
requirement, err := labels.NewRequirement(LabelEnable, selection.Equals, []string{"true"})
if err != nil {
return nil, err
}
requirementDeprecated, err := labels.NewRequirement(discovery.LabelEnable, selection.Equals, []string{"true"})
if err != nil {
return nil, err
}
selector := labels.NewSelector()
selector = selector.Add(*requirement, *requirementDeprecated)
deployments, err := provider.Client.AppsV1().Deployments(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ deployments, err := provider.Client.AppsV1().Deployments(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{
LabelSelector: strings.Join(options.Labels, ","), LabelSelector: selector.String(),
}) })
if err != nil { if err != nil {
@@ -49,23 +67,23 @@ func (provider *KubernetesProvider) deploymentToInstance(d v1.Deployment) types.
var group string var group string
var replicas uint64 var replicas uint64
if _, ok := d.Labels[discovery.LabelEnable]; ok { if _, ok := d.Labels[LabelEnable]; ok {
if g, ok := d.Labels[discovery.LabelGroup]; ok { if g, ok := d.Labels[LabelGroup]; ok {
group = g group = g
} else { } else {
group = discovery.LabelGroupDefaultValue group = LabelGroupDefaultValue
} }
if r, ok := d.Labels[discovery.LabelReplicas]; ok { if r, ok := d.Labels[LabelReplicas]; ok {
atoi, err := strconv.Atoi(r) atoi, err := strconv.Atoi(r)
if err != nil { if err != nil {
log.Warnf("Defaulting to default replicas value, could not convert value \"%v\" to int: %v", r, err) log.Warnf("Defaulting to default replicas value, could not convert value \"%v\" to int: %v", r, err)
replicas = discovery.LabelReplicasDefaultValue replicas = LabelReplicasDefaultValue
} else { } else {
replicas = uint64(atoi) replicas = uint64(atoi)
} }
} else { } else {
replicas = discovery.LabelReplicasDefaultValue replicas = LabelReplicasDefaultValue
} }
} }
@@ -82,9 +100,19 @@ func (provider *KubernetesProvider) deploymentToInstance(d v1.Deployment) types.
} }
} }
func (provider *KubernetesProvider) statefulSetList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { func (provider *KubernetesProvider) statefulSetList(ctx context.Context) ([]types.Instance, error) {
requirement, err := labels.NewRequirement(LabelEnable, selection.Equals, []string{"true"})
if err != nil {
return nil, err
}
requirementDeprecated, err := labels.NewRequirement(discovery.LabelEnable, selection.Equals, []string{"true"})
if err != nil {
return nil, err
}
selector := labels.NewSelector()
selector = selector.Add(*requirement, *requirementDeprecated)
statefulSets, err := provider.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{ statefulSets, err := provider.Client.AppsV1().StatefulSets(core_v1.NamespaceAll).List(ctx, metav1.ListOptions{
LabelSelector: strings.Join(options.Labels, ","), LabelSelector: selector.String(),
}) })
if err != nil { if err != nil {
@@ -104,23 +132,23 @@ func (provider *KubernetesProvider) statefulSetToInstance(ss v1.StatefulSet) typ
var group string var group string
var replicas uint64 var replicas uint64
if _, ok := ss.Labels[discovery.LabelEnable]; ok { if _, ok := ss.Labels[LabelEnable]; ok {
if g, ok := ss.Labels[discovery.LabelGroup]; ok { if g, ok := ss.Labels[LabelGroup]; ok {
group = g group = g
} else { } else {
group = discovery.LabelGroupDefaultValue group = LabelGroupDefaultValue
} }
if r, ok := ss.Labels[discovery.LabelReplicas]; ok { if r, ok := ss.Labels[LabelReplicas]; ok {
atoi, err := strconv.Atoi(r) atoi, err := strconv.Atoi(r)
if err != nil { if err != nil {
log.Warnf("Defaulting to default replicas value, could not convert value \"%v\" to int: %v", r, err) log.Warnf("Defaulting to default replicas value, could not convert value \"%v\" to int: %v", r, err)
replicas = discovery.LabelReplicasDefaultValue replicas = LabelReplicasDefaultValue
} else { } else {
replicas = uint64(atoi) replicas = uint64(atoi)
} }
} else { } else {
replicas = discovery.LabelReplicasDefaultValue replicas = LabelReplicasDefaultValue
} }
} }

View File

@@ -32,8 +32,8 @@ func (m *ProviderMock) GetGroups(ctx context.Context) (map[string][]string, erro
args := m.Called(ctx) args := m.Called(ctx)
return args.Get(0).(map[string][]string), args.Error(1) return args.Get(0).(map[string][]string), args.Error(1)
} }
func (m *ProviderMock) InstanceList(ctx context.Context, options providers.InstanceListOptions) ([]types.Instance, error) { func (m *ProviderMock) List(ctx context.Context) ([]types.Instance, error) {
args := m.Called(ctx, options) args := m.Called(ctx)
return args.Get(0).([]types.Instance), args.Error(1) return args.Get(0).([]types.Instance), args.Error(1)
} }

View File

@@ -12,7 +12,7 @@ type Provider interface {
Stop(ctx context.Context, name string) error Stop(ctx context.Context, name string) error
GetState(ctx context.Context, name string) (instance.State, error) GetState(ctx context.Context, name string) (instance.State, error)
GetGroups(ctx context.Context) (map[string][]string, error) GetGroups(ctx context.Context) (map[string][]string, error)
InstanceList(ctx context.Context, options InstanceListOptions) ([]types.Instance, error) List(ctx context.Context) ([]types.Instance, error)
NotifyInstanceStopped(ctx context.Context, instance chan<- string) NotifyInstanceStopped(ctx context.Context, instance chan<- string)
} }

View File

@@ -1,6 +1,5 @@
package providers package providers
type InstanceListOptions struct { type InstanceListOptions struct {
All bool All bool
Labels []string
} }

View File

@@ -74,17 +74,17 @@ kind: Deployment
metadata: metadata:
name: whoami name: whoami
labels: labels:
app: whoami app.kubernetes.io/name: whoami
sablier.enable: "true" sablierapp.dev/enable: "true"
sablier.group: mygroup sablierapp.dev/group: mygroup
spec: spec:
selector: selector:
matchLabels: matchLabels:
app: whoami app.kubernetes.io/name: whoami
template: template:
metadata: metadata:
labels: labels:
app: whoami app.kubernetes.io/name: whoami
spec: spec:
containers: containers:
- name: whoami - name: whoami

View File

@@ -1,20 +1,20 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: whoami-deployment name: whoami
labels: labels:
app: whoami app.kubernetes.io/name: whoami
sablier.enable: "true" sablierapp.dev/enable: "true"
sablier.group: "E2E" sablierapp.dev/group: "E2E"
spec: spec:
replicas: 0 replicas: 0
selector: selector:
matchLabels: matchLabels:
app: whoami app.kubernetes.io/name: whoami
template: template:
metadata: metadata:
labels: labels:
app: whoami app.kubernetes.io/name: whoami
spec: spec:
containers: containers:
- name: whoami - name: whoami
@@ -36,7 +36,7 @@ spec:
targetPort: 80 targetPort: 80
port: 80 port: 80
selector: selector:
app: whoami app.kubernetes.io/name: whoami
--- ---
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: Middleware kind: Middleware
@@ -160,18 +160,18 @@ kind: Deployment
metadata: metadata:
name: nginx-deployment name: nginx-deployment
labels: labels:
app: nginx app.kubernetes.io/name: nginx
sablier.enable: "true" sablierapp.dev/enable: "true"
sablier.group: "E2E" sablierapp.dev/group: "E2E"
spec: spec:
replicas: 0 replicas: 0
selector: selector:
matchLabels: matchLabels:
app: nginx app.kubernetes.io/name: nginx
template: template:
metadata: metadata:
labels: labels:
app: nginx app.kubernetes.io/name: nginx
spec: spec:
containers: containers:
- name: nginx - name: nginx
@@ -195,7 +195,7 @@ spec:
targetPort: 80 targetPort: 80
port: 80 port: 80
selector: selector:
app: nginx app.kubernetes.io/name: nginx
--- ---
apiVersion: traefik.io/v1alpha1 apiVersion: traefik.io/v1alpha1
kind: Middleware kind: Middleware