Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
feat(provider): add docker swarm provider
Browse files Browse the repository at this point in the history
  • Loading branch information
acouvreur committed Nov 3, 2022
1 parent bbdddca commit 1b14552
Show file tree
Hide file tree
Showing 5 changed files with 830 additions and 39 deletions.
39 changes: 0 additions & 39 deletions app/providers/docker_classic.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ func NewDockerClassicProvider() (*DockerClassicProvider, error) {
}, nil
}

func (provider *DockerClassicProvider) Init() {

}

func (provider *DockerClassicProvider) Start(name string) (InstanceState, error) {
ctx := context.Background()

Expand Down Expand Up @@ -102,38 +98,3 @@ func (provider *DockerClassicProvider) GetState(name string) (InstanceState, err
return unrecoverableInstanceState(name, fmt.Sprintf("container status \"%s\" not handled", spec.State.Status))
}
}

func errorInstanceState(name string, err error) (InstanceState, error) {
log.Error(err.Error())
return InstanceState{
Name: name,
CurrentReplicas: 0,
Status: Error,
Error: err.Error(),
}, err
}

func unrecoverableInstanceState(name string, err string) (InstanceState, error) {
return InstanceState{
Name: name,
CurrentReplicas: 0,
Status: Error,
Error: err,
}, nil
}

func readyInstanceState(name string) (InstanceState, error) {
return InstanceState{
Name: name,
CurrentReplicas: 1,
Status: Ready,
}, nil
}

func notReadyInstanceState(name string) (InstanceState, error) {
return InstanceState{
Name: name,
CurrentReplicas: 0,
Status: NotReady,
}, nil
}
137 changes: 137 additions & 0 deletions app/providers/docker_swarm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package providers

import (
"context"
"fmt"
"strings"

"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.ServiceAPIClient
}

func NewDockerSwarmProvider() (*DockerSwarmProvider, error) {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
log.Fatal(fmt.Errorf("%+v", "Could not connect to docker API"))
return nil, err
}
return &DockerSwarmProvider{
Client: cli,
}, nil
}

func (provider *DockerSwarmProvider) Start(name string) (InstanceState, error) {
return provider.scale(name, 1)
}

func (provider *DockerSwarmProvider) Stop(name string) (InstanceState, error) {
return provider.scale(name, 0)
}

func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (InstanceState, error) {
ctx := context.Background()
service, err := provider.getServiceByName(name, ctx)

if err != nil {
return errorInstanceState(name, err)
}

foundName := provider.getInstanceName(name, *service)

if service.Spec.Mode.Replicated == nil {
return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
}

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 errorInstanceState(foundName, err)
}

if len(response.Warnings) > 0 {
return unrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", "))
}

return notReadyInstanceState(foundName)
}

func (provider *DockerSwarmProvider) GetState(name string) (InstanceState, error) {
ctx := context.Background()

service, err := provider.getServiceByName(name, ctx)
if err != nil {
return errorInstanceState(name, err)
}

foundName := provider.getInstanceName(name, *service)

if service.Spec.Mode.Replicated == nil {
return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
}

if service.ServiceStatus.DesiredTasks != service.ServiceStatus.RunningTasks || service.ServiceStatus.DesiredTasks == 0 {
return notReadyInstanceState(foundName)
}

return readyInstanceState(foundName)
}

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

suffixMatches := make([]swarm.Service, 0)
suffixMatchNames := make([]string, 0)

for _, service := range services {
// Exact match
if service.Spec.Name == name {
return &service, nil
} else if strings.HasSuffix(service.Spec.Name, name) {
suffixMatches = append(suffixMatches, service)
suffixMatchNames = append(suffixMatchNames, service.Spec.Name)
} else {
log.Warnf("service %s was ignored because it did not match %s exactly or on suffix", service.Spec.Name, name)
}
}

if len(suffixMatches) > 1 {
return nil, fmt.Errorf("ambiguous service names found for \"%s\" (%s)", name, strings.Join(suffixMatchNames, ", "))
}

if len(suffixMatches) == 1 {
return &suffixMatches[0], 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)
}
Loading

0 comments on commit 1b14552

Please sign in to comment.