Files
sablier/app/providers/docker_swarm.go
2022-11-11 17:00:35 -05:00

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
}
}
}()
}