221 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			221 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package docker
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/rs/zerolog/log"
 | |
| )
 | |
| 
 | |
| const nbImageParts = 2
 | |
| 
 | |
| type ServiceStatus string
 | |
| 
 | |
| // Docker engine service status
 | |
| const (
 | |
| 	Pending    ServiceStatus = "pending"
 | |
| 	Restarting ServiceStatus = "preparing"
 | |
| 	Starting                 = "starting"
 | |
| 	Running                  = "running"
 | |
| 	Failed                   = "failed"
 | |
| 	Shutdown                 = "shutdown"
 | |
| 	Rejected                 = "rejected"
 | |
| 	Orphaned                 = "orphaned"
 | |
| 	Complete                 = "complete"
 | |
| 	New                      = "new"
 | |
| 	Unknown                  = "unknown"
 | |
| )
 | |
| 
 | |
| var mapServiceStatus = map[string]ServiceStatus{
 | |
| 	"pending":    Pending,
 | |
| 	"restarting": Restarting,
 | |
| 	"starting":   Starting,
 | |
| 	"running":    Running,
 | |
| 	"failed":     Failed,
 | |
| 	"shutdown":   Shutdown,
 | |
| 	"rejected":   Rejected,
 | |
| 	"orphaned":   Orphaned,
 | |
| 	"complete":   Complete,
 | |
| 	"new":        New,
 | |
| }
 | |
| 
 | |
| func serviceStatusFromString(value string) ServiceStatus {
 | |
| 	if v, ok := mapServiceStatus[strings.ToLower(value)]; ok {
 | |
| 		return v
 | |
| 	}
 | |
| 	return Unknown
 | |
| }
 | |
| 
 | |
| func getReplicas(states []serviceState, nbReplicas int) []Replicas {
 | |
| 	replicas := []Replicas{}
 | |
| 
 | |
| 	// collect latest services for each replicas
 | |
| 	rep := 1
 | |
| 	for idx := range states {
 | |
| 		if rep > nbReplicas {
 | |
| 			break
 | |
| 		}
 | |
| 		if states[idx].ReplicasNumber == rep {
 | |
| 			replicas = append(replicas, Replicas{
 | |
| 				Pos:   rep,
 | |
| 				State: serviceStatusFromString(states[idx].Current),
 | |
| 				Error: states[idx].Error,
 | |
| 			})
 | |
| 			rep++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return replicas
 | |
| }
 | |
| 
 | |
| type Replicas struct {
 | |
| 	State ServiceStatus
 | |
| 	Error string
 | |
| 	Pos   int
 | |
| }
 | |
| 
 | |
| type ServicePort struct {
 | |
| 	Target    int `json:"TargetPort"`
 | |
| 	Published int `json:"PublishedPort"`
 | |
| }
 | |
| 
 | |
| type serviceDetails struct {
 | |
| 	ID        string    `json:"ID"`
 | |
| 	CreateAt  time.Time `json:"CreatedAt"`
 | |
| 	UpdatedAt time.Time `json:"UpdatedAt"`
 | |
| 	Spec      struct {  //nolint:govet // fieldalignment
 | |
| 		Name         string `json:"Name"`
 | |
| 		TaskTemplate struct {
 | |
| 			Networks []struct {
 | |
| 				ID string `json:"Target"`
 | |
| 			} `json:"Networks"`
 | |
| 		} `json:"TaskTemplate"`
 | |
| 		Labels struct {
 | |
| 			Image     string `json:"com.docker.stack.image"`
 | |
| 			Namespace string `json:"com.docker.stack.namespace"`
 | |
| 		} `json:"Labels"`
 | |
| 		Mode struct {
 | |
| 			Replicated struct {
 | |
| 				Replicas int `json:"Replicas"`
 | |
| 			} `json:"Replicated"`
 | |
| 		} `json:"Mode"`
 | |
| 	} `json:"spec"`
 | |
| 	Endpoint struct {
 | |
| 		Ports []ServicePort `json:"Ports"`
 | |
| 	} `json:"Endpoint"`
 | |
| }
 | |
| 
 | |
| type serviceStateInput serviceState
 | |
| 
 | |
| type serviceState struct {
 | |
| 	Name           string `json:"Name"`
 | |
| 	Current        string `json:"CurrentState"`
 | |
| 	Desired        string `json:"DesiredState"`
 | |
| 	Error          string `json:"Error"`
 | |
| 	ReplicasNumber int
 | |
| }
 | |
| 
 | |
| func (st *serviceState) UnmarshalJSON(data []byte) error {
 | |
| 	state := serviceStateInput{}
 | |
| 	if err := json.Unmarshal(data, &state); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	nameParts := strings.Split(state.Name, ".")
 | |
| 	if len(nameParts) > 0 {
 | |
| 		replicasNumber, err := strconv.Atoi(nameParts[1])
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		state.ReplicasNumber = replicasNumber
 | |
| 	}
 | |
| 
 | |
| 	currentStateParts := strings.Split(state.Current, ",")
 | |
| 	if len(currentStateParts) > 0 {
 | |
| 		state.Current = currentStateParts[0]
 | |
| 	}
 | |
| 
 | |
| 	*st = (serviceState)(state)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type serviceCommandInput struct {
 | |
| 	States  []serviceState   `json:"services"`
 | |
| 	Details []serviceDetails `json:"details"`
 | |
| }
 | |
| 
 | |
| type Service struct {
 | |
| 	ID    string
 | |
| 	App   string
 | |
| 	Name  string
 | |
| 	Image struct {
 | |
| 		Name string
 | |
| 		Tag  string
 | |
| 	}
 | |
| 	Networks []string
 | |
| 	Replicas []Replicas
 | |
| 	Ports    []ServicePort
 | |
| }
 | |
| 
 | |
| func (s *Service) UnmarshalJSON(data []byte) error {
 | |
| 	ci := serviceCommandInput{}
 | |
| 	if err := json.Unmarshal(data, &ci); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if len(ci.Details) == 0 {
 | |
| 		return fmt.Errorf("service details must no be empty")
 | |
| 	}
 | |
| 
 | |
| 	nbReplicas := ci.Details[0].Spec.Mode.Replicated.Replicas
 | |
| 	if len(ci.States) < nbReplicas {
 | |
| 		log.Warn().Msg(fmt.Sprintf("must have %d replicas but have %d", nbReplicas, len(ci.States)))
 | |
| 	}
 | |
| 
 | |
| 	networks := []string{}
 | |
| 	for idx := range ci.Details[0].Spec.TaskTemplate.Networks {
 | |
| 		networks = append(networks, ci.Details[0].Spec.TaskTemplate.Networks[idx].ID)
 | |
| 	}
 | |
| 
 | |
| 	replicas := getReplicas(ci.States, nbReplicas)
 | |
| 
 | |
| 	imageName := ci.Details[0].Spec.Labels.Image
 | |
| 	imageNameParts := strings.Split(imageName, ":")
 | |
| 	if len(imageNameParts) == nbImageParts {
 | |
| 		imageName = imageNameParts[0]
 | |
| 	}
 | |
| 
 | |
| 	appName := ci.Details[0].Spec.Labels.Namespace
 | |
| 
 | |
| 	*s = Service{
 | |
| 		ID:   ci.Details[0].ID,
 | |
| 		App:  appName,
 | |
| 		Name: strings.TrimPrefix(ci.Details[0].Spec.Name, appName+"_"),
 | |
| 		Image: struct {
 | |
| 			Name string
 | |
| 			Tag  string
 | |
| 		}{
 | |
| 			Name: imageName,
 | |
| 			Tag:  imageNameParts[1],
 | |
| 		},
 | |
| 		Networks: networks,
 | |
| 		Ports:    ci.Details[0].Endpoint.Ports,
 | |
| 		Replicas: replicas,
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type Services []Service
 | |
| 
 | |
| func (s Services) GetIDs() []string {
 | |
| 	ids := []string{}
 | |
| 	for idx := range s {
 | |
| 		ids = append(ids, s[idx].ID)
 | |
| 	}
 | |
| 	return ids
 | |
| }
 | 
