hmdeploy/docker/models.go
2025-10-28 21:43:18 +01:00

226 lines
4.8 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]
}
tag := "?"
if len(imageNameParts) > 1 {
tag = imageNameParts[1]
}
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: tag,
},
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
}