mirror of
https://github.com/sablierapp/sablier.git
synced 2025-12-21 21:33:06 +01:00
154 lines
4.2 KiB
Go
154 lines
4.2 KiB
Go
package providers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/acouvreur/sablier/app/instance"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/client"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type DockerSwarmProvider struct {
|
|
Client client.APIClient
|
|
desiredReplicas int
|
|
}
|
|
|
|
func NewDockerSwarmProvider() (*DockerSwarmProvider, error) {
|
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &DockerSwarmProvider{
|
|
Client: cli,
|
|
desiredReplicas: 1,
|
|
}, nil
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) Start(name string) (instance.State, error) {
|
|
return provider.scale(name, uint64(provider.desiredReplicas))
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) Stop(name string) (instance.State, error) {
|
|
return provider.scale(name, 0)
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (instance.State, error) {
|
|
ctx := context.Background()
|
|
service, err := provider.getServiceByName(name, ctx)
|
|
|
|
if err != nil {
|
|
return instance.ErrorInstanceState(name, err, provider.desiredReplicas)
|
|
}
|
|
|
|
foundName := provider.getInstanceName(name, *service)
|
|
|
|
if service.Spec.Mode.Replicated == nil {
|
|
return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode", provider.desiredReplicas)
|
|
}
|
|
|
|
service.Spec.Mode.Replicated.Replicas = &replicas
|
|
|
|
response, err := provider.Client.ServiceUpdate(ctx, service.ID, service.Meta.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
|
|
if err != nil {
|
|
return instance.ErrorInstanceState(foundName, err, provider.desiredReplicas)
|
|
}
|
|
|
|
if len(response.Warnings) > 0 {
|
|
return instance.UnrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", "), provider.desiredReplicas)
|
|
}
|
|
|
|
return instance.NotReadyInstanceState(foundName, 0, provider.desiredReplicas)
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) GetState(name string) (instance.State, error) {
|
|
ctx := context.Background()
|
|
|
|
service, err := provider.getServiceByName(name, ctx)
|
|
if err != nil {
|
|
return instance.ErrorInstanceState(name, err, provider.desiredReplicas)
|
|
}
|
|
|
|
foundName := provider.getInstanceName(name, *service)
|
|
|
|
if service.Spec.Mode.Replicated == nil {
|
|
return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode", provider.desiredReplicas)
|
|
}
|
|
|
|
if service.ServiceStatus.DesiredTasks != service.ServiceStatus.RunningTasks || service.ServiceStatus.DesiredTasks == 0 {
|
|
return instance.NotReadyInstanceState(foundName, 0, provider.desiredReplicas)
|
|
}
|
|
|
|
return instance.ReadyInstanceState(foundName, provider.desiredReplicas)
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) getServiceByName(name string, ctx context.Context) (*swarm.Service, error) {
|
|
opts := types.ServiceListOptions{
|
|
Filters: filters.NewArgs(),
|
|
Status: true,
|
|
}
|
|
opts.Filters.Add("name", name)
|
|
|
|
services, err := provider.Client.ServiceList(ctx, opts)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(services) == 0 {
|
|
return nil, fmt.Errorf(fmt.Sprintf("service with name %s was not found", name))
|
|
}
|
|
|
|
for _, service := range services {
|
|
// Exact match
|
|
if service.Spec.Name == name {
|
|
return &service, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf(fmt.Sprintf("service %s was not found because it did not match exactly or on suffix", name))
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) getInstanceName(name string, service swarm.Service) string {
|
|
if name == service.Spec.Name {
|
|
return name
|
|
}
|
|
|
|
return fmt.Sprintf("%s (%s)", name, service.Spec.Name)
|
|
}
|
|
|
|
func (provider *DockerSwarmProvider) NotifyInsanceStopped(ctx context.Context, instance chan<- string) {
|
|
msgs, errs := provider.Client.Events(ctx, types.EventsOptions{
|
|
Filters: filters.NewArgs(
|
|
filters.Arg("scope", "swarm"),
|
|
filters.Arg("type", "service"),
|
|
),
|
|
})
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case msg := <-msgs:
|
|
// Send the container that has died to the channel
|
|
if msg.Actor.Attributes["replicas.new"] == "0" {
|
|
instance <- msg.Actor.Attributes["name"]
|
|
}
|
|
case err := <-errs:
|
|
if errors.Is(err, io.EOF) {
|
|
log.Debug("provider event stream closed")
|
|
return
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|