2021-12-06 12:24:22 +00:00
|
|
|
package biz
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/cuigh/auxo/byte/size"
|
|
|
|
"github.com/cuigh/auxo/data"
|
|
|
|
"github.com/cuigh/auxo/net/web"
|
|
|
|
"github.com/cuigh/swirl/docker"
|
|
|
|
"github.com/docker/docker/api/types/mount"
|
|
|
|
"github.com/docker/docker/api/types/swarm"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ServiceModeReplicated = "replicated"
|
|
|
|
ServiceModeGlobal = "global"
|
|
|
|
ServiceModeReplicatedJob = "replicated-job"
|
|
|
|
ServiceModeGlobalJob = "global-job"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ServiceBiz interface {
|
2022-01-06 08:54:14 +00:00
|
|
|
Search(ctx context.Context, name, mode string, pageIndex, pageSize int) (services []*ServiceBase, total int, err error)
|
|
|
|
Find(ctx context.Context, name string, status bool) (service *Service, raw string, err error)
|
|
|
|
Delete(ctx context.Context, name string, user web.User) (err error)
|
|
|
|
Rollback(ctx context.Context, name string, user web.User) (err error)
|
|
|
|
Restart(ctx context.Context, name string, user web.User) (err error)
|
|
|
|
Scale(ctx context.Context, name string, count, version uint64, user web.User) (err error)
|
|
|
|
Create(ctx context.Context, s *Service, user web.User) (err error)
|
|
|
|
Update(ctx context.Context, s *Service, user web.User) (err error)
|
|
|
|
FetchLogs(ctx context.Context, name string, lines int, timestamps bool) (stdout, stderr string, err error)
|
2021-12-06 12:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewService(d *docker.Docker, rb RegistryBiz, eb EventBiz) ServiceBiz {
|
|
|
|
return &serviceBiz{d: d, rb: rb, eb: eb}
|
|
|
|
}
|
|
|
|
|
|
|
|
type serviceBiz struct {
|
|
|
|
d *docker.Docker
|
|
|
|
rb RegistryBiz
|
|
|
|
eb EventBiz
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Find(ctx context.Context, name string, status bool) (service *Service, raw string, err error) {
|
2021-12-06 12:24:22 +00:00
|
|
|
var (
|
|
|
|
s swarm.Service
|
|
|
|
r []byte
|
|
|
|
)
|
2021-12-24 11:52:29 +00:00
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
s, r, err = b.d.ServiceInspect(ctx, name, status)
|
2021-12-24 11:52:29 +00:00
|
|
|
if err != nil {
|
|
|
|
if docker.IsErrNotFound(err) {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
|
|
|
raw, err = indentJSON(r)
|
|
|
|
}
|
|
|
|
if err == nil {
|
|
|
|
service = newService(&s)
|
2022-01-06 08:54:14 +00:00
|
|
|
err = b.fillNetworks(ctx, service)
|
2021-12-06 12:24:22 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) fillNetworks(ctx context.Context, service *Service) error {
|
2021-12-23 06:40:14 +00:00
|
|
|
if len(service.Endpoint.VIPs) == 0 {
|
2021-12-06 12:24:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-23 06:40:14 +00:00
|
|
|
var ids = make([]string, len(service.Endpoint.VIPs))
|
|
|
|
for i, vip := range service.Endpoint.VIPs {
|
|
|
|
ids[i] = vip.ID
|
2021-12-06 12:24:22 +00:00
|
|
|
}
|
2021-12-23 06:40:14 +00:00
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
names, err := b.d.NetworkNames(ctx, ids...)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
2021-12-23 06:40:14 +00:00
|
|
|
for i := range service.Endpoint.VIPs {
|
|
|
|
vip := &service.Endpoint.VIPs[i]
|
|
|
|
vip.Name = names[vip.ID]
|
|
|
|
// ingress network cannot be explicitly attached.
|
|
|
|
if vip.Name != "ingress" {
|
|
|
|
service.Networks = append(service.Networks, vip.Name)
|
|
|
|
}
|
2021-12-06 12:24:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Search(ctx context.Context, name, mode string, pageIndex, pageSize int) (services []*ServiceBase, total int, err error) {
|
2021-12-06 12:24:22 +00:00
|
|
|
var list []swarm.Service
|
2022-01-06 08:54:14 +00:00
|
|
|
list, total, err = b.d.ServiceList(ctx, name, mode, pageIndex, pageSize)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
services = make([]*ServiceBase, len(list))
|
|
|
|
for i, s := range list {
|
|
|
|
services[i] = newServiceBase(&s)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Delete(ctx context.Context, name string, user web.User) (err error) {
|
|
|
|
err = b.d.ServiceRemove(ctx, name)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
|
|
|
b.eb.CreateService(EventActionDelete, name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Rollback(ctx context.Context, name string, user web.User) (err error) {
|
|
|
|
err = b.d.ServiceRollback(ctx, name)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
|
|
|
b.eb.CreateService(EventActionRollback, name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Restart(ctx context.Context, name string, user web.User) (err error) {
|
|
|
|
err = b.d.ServiceRestart(ctx, name)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
|
|
|
b.eb.CreateService(EventActionRestart, name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Scale(ctx context.Context, name string, count, version uint64, user web.User) (err error) {
|
|
|
|
err = b.d.ServiceScale(ctx, name, count, version)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err == nil {
|
|
|
|
b.eb.CreateService(EventActionScale, name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Create(ctx context.Context, s *Service, user web.User) (err error) {
|
2021-12-06 12:24:22 +00:00
|
|
|
spec := &swarm.ServiceSpec{TaskTemplate: swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{}}}
|
|
|
|
err = s.MergeTo(spec)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-17 12:13:58 +00:00
|
|
|
if s.Mode == "replicated" {
|
|
|
|
spec.Mode.Replicated = &swarm.ReplicatedService{Replicas: &s.Replicas}
|
|
|
|
} else if s.Mode == "replicated-job" {
|
|
|
|
spec.Mode.ReplicatedJob = &swarm.ReplicatedJob{TotalCompletions: &s.Replicas}
|
|
|
|
} else if s.Mode == "global" {
|
|
|
|
spec.Mode.Global = &swarm.GlobalService{}
|
|
|
|
} else if s.Mode == "global-job" {
|
|
|
|
spec.Mode.GlobalJob = &swarm.GlobalJob{}
|
|
|
|
}
|
|
|
|
|
2021-12-06 12:24:22 +00:00
|
|
|
auth := ""
|
|
|
|
if i := strings.Index(s.Image, "/"); i > 0 {
|
|
|
|
if host := s.Image[:i]; strings.Contains(host, ".") {
|
2022-01-06 08:54:14 +00:00
|
|
|
auth, err = b.rb.GetAuth(ctx, host)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
if err = b.d.ServiceCreate(ctx, spec, auth); err == nil {
|
2021-12-06 12:24:22 +00:00
|
|
|
b.eb.CreateService(EventActionCreate, s.Name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) Update(ctx context.Context, s *Service, user web.User) (err error) {
|
|
|
|
service, _, err := b.d.ServiceInspect(ctx, s.Name, false)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := &service.Spec
|
|
|
|
err = s.MergeTo(spec)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-17 12:13:58 +00:00
|
|
|
if s.Mode == "replicated" && spec.Mode.Replicated != nil {
|
|
|
|
spec.Mode.Replicated.Replicas = &s.Replicas
|
|
|
|
} else if s.Mode == "replicated-job" && spec.Mode.ReplicatedJob != nil {
|
|
|
|
spec.Mode.ReplicatedJob.TotalCompletions = &s.Replicas
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
if err = b.d.ServiceUpdate(ctx, spec, s.Version); err == nil {
|
2021-12-06 12:24:22 +00:00
|
|
|
b.eb.CreateService(EventActionUpdate, s.Name, user)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-06 08:54:14 +00:00
|
|
|
func (b *serviceBiz) FetchLogs(ctx context.Context, name string, lines int, timestamps bool) (string, string, error) {
|
|
|
|
stdout, stderr, err := b.d.ServiceLogs(ctx, name, lines, timestamps)
|
2021-12-06 12:24:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", "", err
|
|
|
|
}
|
|
|
|
return stdout.String(), stderr.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceBase struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Image string `json:"image"`
|
|
|
|
Mode string `json:"mode"`
|
|
|
|
Replicas uint64 `json:"replicas"`
|
|
|
|
DesiredTasks uint64 `json:"desiredTasks"`
|
|
|
|
RunningTasks uint64 `json:"runningTasks"`
|
|
|
|
CompletedTasks uint64 `json:"completedTasks"`
|
|
|
|
CreatedAt string `json:"createdAt"`
|
|
|
|
UpdatedAt string `json:"updatedAt"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Service struct {
|
|
|
|
ServiceBase
|
|
|
|
ID string `json:"id"`
|
|
|
|
Version uint64 `json:"version"`
|
|
|
|
Command string `json:"command"`
|
|
|
|
Args string `json:"args"`
|
|
|
|
Dir string `json:"dir"`
|
|
|
|
User string `json:"user"`
|
|
|
|
Hostname string `json:"hostname"`
|
|
|
|
Env data.Options `json:"env,omitempty"`
|
|
|
|
Labels data.Options `json:"labels,omitempty"`
|
|
|
|
ContainerLabels data.Options `json:"containerLabels,omitempty"`
|
2021-12-23 06:40:14 +00:00
|
|
|
Networks []string `json:"networks,omitempty"` // only for edit
|
2021-12-06 12:24:22 +00:00
|
|
|
Mounts []Mount `json:"mounts,omitempty"`
|
|
|
|
Update struct {
|
|
|
|
State string `json:"state,omitempty"`
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
} `json:"update,omitempty"`
|
|
|
|
Endpoint struct {
|
|
|
|
Mode swarm.ResolutionMode `json:"mode,omitempty"`
|
|
|
|
Ports []EndpointPort `json:"ports,omitempty"`
|
|
|
|
VIPs []EndpointVIP `json:"vips,omitempty"`
|
|
|
|
} `json:"endpoint"`
|
|
|
|
Configs []*ServiceFile `json:"configs"`
|
|
|
|
Secrets []*ServiceFile `json:"secrets"`
|
|
|
|
UpdatePolicy UpdatePolicy `json:"updatePolicy"`
|
|
|
|
RollbackPolicy UpdatePolicy `json:"rollbackPolicy"`
|
|
|
|
RestartPolicy RestartPolicy `json:"restartPolicy"`
|
|
|
|
Resource struct {
|
|
|
|
Limit ServiceResource `json:"limit"`
|
|
|
|
Reserve ServiceResource `json:"reserve"`
|
|
|
|
} `json:"resource"`
|
|
|
|
Placement struct {
|
|
|
|
Constraints []PlacementConstraint `json:"constraints"`
|
|
|
|
Preferences []string `json:"preferences"`
|
|
|
|
//Platforms []Platform `json:"platforms"`
|
|
|
|
} `json:"placement"`
|
|
|
|
LogDriver struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Options data.Options `json:"options"`
|
|
|
|
} `json:"logDriver"`
|
|
|
|
DNS struct {
|
|
|
|
Servers []string `json:"servers"`
|
|
|
|
Search []string `json:"search"`
|
|
|
|
Options []string `json:"options"`
|
|
|
|
} `json:"dns"`
|
|
|
|
Hosts []string `json:"hosts"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type PlacementConstraint struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
Operator string `json:"op"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPlacementConstraint(c string) PlacementConstraint {
|
|
|
|
var (
|
|
|
|
pc = PlacementConstraint{}
|
|
|
|
items []string
|
|
|
|
)
|
|
|
|
|
|
|
|
if items = strings.SplitN(c, "==", 2); len(items) == 2 {
|
|
|
|
pc.Operator = "=="
|
|
|
|
} else if items = strings.SplitN(c, "!=", 2); len(items) == 2 {
|
|
|
|
pc.Operator = "!="
|
|
|
|
}
|
|
|
|
if pc.Operator != "" {
|
|
|
|
pc.Name = strings.TrimSpace(items[0])
|
|
|
|
pc.Value = strings.TrimSpace(items[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
return pc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PlacementConstraint) ToConstraint() string {
|
|
|
|
if pc.Name != "" && pc.Value != "" && pc.Operator != "" {
|
|
|
|
return fmt.Sprintf("%s %s %s", pc.Name, pc.Operator, pc.Value)
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceResource struct {
|
|
|
|
CPU float64 `json:"cpu,omitempty"`
|
|
|
|
Memory string `json:"memory,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceResourceFromResources(res *swarm.Resources) ServiceResource {
|
|
|
|
ri := ServiceResource{}
|
|
|
|
if res != nil {
|
|
|
|
ri.CPU = float64(res.NanoCPUs) / 1e9
|
|
|
|
if res.MemoryBytes > 0 {
|
|
|
|
ri.Memory = size.Size(res.MemoryBytes).String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ri
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceResourceFromLimit(res *swarm.Limit) ServiceResource {
|
|
|
|
ri := ServiceResource{}
|
|
|
|
if res != nil {
|
|
|
|
ri.CPU = float64(res.NanoCPUs) / 1e9
|
|
|
|
if res.MemoryBytes > 0 {
|
|
|
|
ri.Memory = size.Size(res.MemoryBytes).String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ri
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r ServiceResource) IsSet() bool {
|
|
|
|
return r.CPU > 0 || r.Memory != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r ServiceResource) ToResources() (res *swarm.Resources, err error) {
|
|
|
|
res = &swarm.Resources{
|
|
|
|
NanoCPUs: int64(r.CPU * 1e9),
|
|
|
|
}
|
|
|
|
if r.Memory != "" {
|
|
|
|
var s size.Size
|
|
|
|
if s, err = size.Parse(r.Memory); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res.MemoryBytes = int64(s)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r ServiceResource) ToLimit() (res *swarm.Limit, err error) {
|
|
|
|
res = &swarm.Limit{
|
|
|
|
NanoCPUs: int64(r.CPU * 1e9),
|
|
|
|
}
|
|
|
|
if r.Memory != "" {
|
|
|
|
var s size.Size
|
|
|
|
if s, err = size.Parse(r.Memory); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res.MemoryBytes = int64(s)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// EndpointPort represents the config of a port.
|
|
|
|
type EndpointPort struct {
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Protocol swarm.PortConfigProtocol `json:"protocol,omitempty"`
|
|
|
|
TargetPort uint32 `json:"targetPort,omitempty"`
|
|
|
|
PublishedPort uint32 `json:"publishedPort,omitempty"`
|
|
|
|
PublishMode swarm.PortConfigPublishMode `json:"mode,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// EndpointVIP represents the virtual ip of a port.
|
|
|
|
type EndpointVIP struct {
|
|
|
|
ID string `json:"id,omitempty"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
IP string `json:"ip,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ServiceFile struct {
|
|
|
|
//ID string `json:"id"`
|
|
|
|
//Name string `json:"name"`
|
|
|
|
Key string `json:"key"` // ID:Name
|
|
|
|
Path string `json:"path"`
|
|
|
|
GID string `json:"gid"`
|
|
|
|
UID string `json:"uid"`
|
|
|
|
Mode uint32 `json:"mode"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceFile(id, name string, filename, uid, gid string, mode os.FileMode) *ServiceFile {
|
|
|
|
m, _ := strconv.ParseUint(strconv.FormatUint(uint64(mode), 8), 10, 32)
|
|
|
|
return &ServiceFile{
|
|
|
|
Key: id + ":" + name,
|
|
|
|
Path: filename,
|
|
|
|
UID: uid,
|
|
|
|
GID: gid,
|
|
|
|
Mode: uint32(m),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *ServiceFile) ToConfig() *swarm.ConfigReference {
|
|
|
|
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(f.Mode), 10), 8, 32)
|
|
|
|
pair := strings.Split(f.Key, ":")
|
|
|
|
return &swarm.ConfigReference{
|
|
|
|
ConfigID: pair[0],
|
|
|
|
ConfigName: pair[1],
|
|
|
|
File: &swarm.ConfigReferenceFileTarget{
|
|
|
|
Name: f.Path,
|
|
|
|
UID: f.UID,
|
|
|
|
GID: f.GID,
|
|
|
|
Mode: os.FileMode(mode),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *ServiceFile) ToSecret() *swarm.SecretReference {
|
|
|
|
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(f.Mode), 10), 8, 32)
|
|
|
|
pair := strings.Split(f.Key, ":")
|
|
|
|
return &swarm.SecretReference{
|
|
|
|
SecretID: pair[0],
|
|
|
|
SecretName: pair[1],
|
|
|
|
File: &swarm.SecretReferenceFileTarget{
|
|
|
|
Name: f.Path,
|
|
|
|
UID: f.UID,
|
|
|
|
GID: f.GID,
|
|
|
|
Mode: os.FileMode(mode),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mount represents a mount (volume).
|
|
|
|
type Mount struct {
|
|
|
|
Type mount.Type `json:"type,omitempty"`
|
|
|
|
Source string `json:"source,omitempty"`
|
|
|
|
Target string `json:"target,omitempty"`
|
|
|
|
Readonly bool `json:"readonly,omitempty"`
|
|
|
|
Consistency mount.Consistency `json:"consistency,omitempty"`
|
|
|
|
//BindOptions *BindOptions `json:",omitempty"`
|
|
|
|
//VolumeOptions *VolumeOptions `json:",omitempty"`
|
|
|
|
//TmpfsOptions *TmpfsOptions `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type UpdatePolicy struct {
|
|
|
|
Parallelism uint64 `json:"parallelism,omitempty"`
|
|
|
|
Delay string `json:"delay,omitempty"`
|
|
|
|
FailureAction string `json:"failureAction,omitempty"`
|
|
|
|
Order string `json:"order,omitempty"`
|
|
|
|
//Monitor time.Duration `json:",omitempty"`
|
|
|
|
//MaxFailureRatio float32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *UpdatePolicy) Convert() *swarm.UpdateConfig {
|
|
|
|
if p.Parallelism == 0 && p.Delay == "" && p.FailureAction == "" && p.Order == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
delay, _ := time.ParseDuration(p.Delay)
|
|
|
|
return &swarm.UpdateConfig{
|
|
|
|
Parallelism: p.Parallelism,
|
|
|
|
Delay: delay,
|
|
|
|
FailureAction: p.FailureAction,
|
|
|
|
Order: p.Order,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newUpdatePolicy(c *swarm.UpdateConfig) UpdatePolicy {
|
|
|
|
p := UpdatePolicy{}
|
|
|
|
if c != nil {
|
|
|
|
p.Parallelism = c.Parallelism
|
|
|
|
p.Delay = c.Delay.String()
|
|
|
|
p.FailureAction = c.FailureAction
|
|
|
|
p.Order = c.Order
|
|
|
|
}
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
type RestartPolicy struct {
|
|
|
|
Condition swarm.RestartPolicyCondition `json:"condition,omitempty"`
|
|
|
|
Delay string `json:"delay,omitempty"`
|
|
|
|
MaxAttempts uint64 `json:"maxAttempts,omitempty"`
|
|
|
|
Window string `json:"window,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *RestartPolicy) Convert() *swarm.RestartPolicy {
|
|
|
|
if p.MaxAttempts == 0 && p.Delay == "" && p.Condition == "" && p.Window == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
policy := &swarm.RestartPolicy{Condition: p.Condition}
|
|
|
|
if delay, err := time.ParseDuration(p.Delay); err == nil {
|
|
|
|
policy.Delay = &delay
|
|
|
|
}
|
|
|
|
if window, err := time.ParseDuration(p.Window); err == nil {
|
|
|
|
policy.Window = &window
|
|
|
|
}
|
|
|
|
if p.MaxAttempts > 0 {
|
|
|
|
policy.MaxAttempts = &p.MaxAttempts
|
|
|
|
}
|
|
|
|
return policy
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRestartPolicy(p *swarm.RestartPolicy) RestartPolicy {
|
|
|
|
policy := RestartPolicy{}
|
|
|
|
if p != nil {
|
|
|
|
policy.Condition = p.Condition
|
|
|
|
if p.Delay != nil {
|
|
|
|
policy.Delay = p.Delay.String()
|
|
|
|
}
|
|
|
|
if p.MaxAttempts != nil {
|
|
|
|
policy.MaxAttempts = *p.MaxAttempts
|
|
|
|
}
|
|
|
|
if p.Window != nil {
|
|
|
|
policy.Window = p.Window.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return policy
|
|
|
|
}
|
|
|
|
|
|
|
|
func newServiceBase(s *swarm.Service) *ServiceBase {
|
|
|
|
service := &ServiceBase{
|
|
|
|
Name: s.Spec.Name,
|
|
|
|
Image: normalizeImage(s.Spec.TaskTemplate.ContainerSpec.Image),
|
|
|
|
CreatedAt: formatTime(s.CreatedAt),
|
|
|
|
UpdatedAt: formatTime(s.UpdatedAt),
|
|
|
|
}
|
|
|
|
|
2021-12-17 12:13:58 +00:00
|
|
|
if s.ServiceStatus != nil {
|
2021-12-06 12:24:22 +00:00
|
|
|
service.RunningTasks = s.ServiceStatus.RunningTasks
|
|
|
|
service.DesiredTasks = s.ServiceStatus.DesiredTasks
|
|
|
|
service.CompletedTasks = s.ServiceStatus.CompletedTasks
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Spec.Mode.Replicated != nil {
|
|
|
|
service.Mode = ServiceModeReplicated
|
|
|
|
service.Replicas = *s.Spec.Mode.Replicated.Replicas
|
|
|
|
} else if s.Spec.Mode.Global != nil {
|
|
|
|
service.Mode = ServiceModeGlobal
|
|
|
|
} else if s.Spec.Mode.ReplicatedJob != nil {
|
|
|
|
service.Mode = ServiceModeReplicatedJob
|
|
|
|
service.Replicas = *s.Spec.Mode.ReplicatedJob.TotalCompletions
|
|
|
|
} else if s.Spec.Mode.GlobalJob != nil {
|
|
|
|
service.Mode = ServiceModeGlobalJob
|
|
|
|
}
|
|
|
|
return service
|
|
|
|
}
|
|
|
|
|
|
|
|
func newService(s *swarm.Service) *Service {
|
|
|
|
service := &Service{
|
|
|
|
ServiceBase: *newServiceBase(s),
|
|
|
|
ID: s.ID,
|
|
|
|
Version: s.Version.Index,
|
|
|
|
Env: envToOptions(s.Spec.TaskTemplate.ContainerSpec.Env),
|
|
|
|
Labels: mapToOptions(s.Spec.Labels),
|
|
|
|
ContainerLabels: mapToOptions(s.Spec.TaskTemplate.ContainerSpec.Labels),
|
|
|
|
Command: strings.Join(s.Spec.TaskTemplate.ContainerSpec.Command, " "),
|
|
|
|
Args: strings.Join(s.Spec.TaskTemplate.ContainerSpec.Args, " "),
|
|
|
|
Dir: s.Spec.TaskTemplate.ContainerSpec.Dir,
|
|
|
|
User: s.Spec.TaskTemplate.ContainerSpec.User,
|
|
|
|
UpdatePolicy: newUpdatePolicy(s.Spec.UpdateConfig),
|
|
|
|
RollbackPolicy: newUpdatePolicy(s.Spec.RollbackConfig),
|
|
|
|
RestartPolicy: newRestartPolicy(s.Spec.TaskTemplate.RestartPolicy),
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.UpdateStatus != nil {
|
|
|
|
service.Update.State = string(s.UpdateStatus.State)
|
|
|
|
service.Update.Message = s.UpdateStatus.Message
|
|
|
|
}
|
|
|
|
|
|
|
|
// Endpoint
|
|
|
|
service.Endpoint.Mode = s.Endpoint.Spec.Mode
|
|
|
|
for _, vip := range s.Endpoint.VirtualIPs {
|
|
|
|
service.Endpoint.VIPs = append(service.Endpoint.VIPs, EndpointVIP{
|
|
|
|
ID: vip.NetworkID,
|
|
|
|
IP: vip.Addr,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, p := range s.Endpoint.Ports {
|
|
|
|
service.Endpoint.Ports = append(service.Endpoint.Ports, EndpointPort{
|
|
|
|
Name: p.Name,
|
|
|
|
Protocol: p.Protocol,
|
|
|
|
TargetPort: p.TargetPort,
|
|
|
|
PublishedPort: p.PublishedPort,
|
|
|
|
PublishMode: p.PublishMode,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mounts
|
|
|
|
if l := len(s.Spec.TaskTemplate.ContainerSpec.Mounts); l > 0 {
|
|
|
|
service.Mounts = make([]Mount, l)
|
|
|
|
for i, m := range s.Spec.TaskTemplate.ContainerSpec.Mounts {
|
|
|
|
service.Mounts[i] = Mount{
|
|
|
|
Type: m.Type,
|
|
|
|
Source: m.Source,
|
|
|
|
Target: m.Target,
|
|
|
|
Readonly: m.ReadOnly,
|
|
|
|
Consistency: m.Consistency,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Files
|
|
|
|
for _, f := range s.Spec.TaskTemplate.ContainerSpec.Configs {
|
|
|
|
file := newServiceFile(f.ConfigID, f.ConfigName, f.File.Name, f.File.UID, f.File.GID, f.File.Mode)
|
|
|
|
service.Configs = append(service.Configs, file)
|
|
|
|
}
|
|
|
|
for _, f := range s.Spec.TaskTemplate.ContainerSpec.Secrets {
|
|
|
|
file := newServiceFile(f.SecretID, f.SecretName, f.File.Name, f.File.UID, f.File.GID, f.File.Mode)
|
|
|
|
service.Secrets = append(service.Secrets, file)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resource
|
|
|
|
if s.Spec.TaskTemplate.Resources != nil {
|
|
|
|
service.Resource.Limit = newServiceResourceFromLimit(s.Spec.TaskTemplate.Resources.Limits)
|
|
|
|
service.Resource.Reserve = newServiceResourceFromResources(s.Spec.TaskTemplate.Resources.Reservations)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Placement
|
|
|
|
if s.Spec.TaskTemplate.Placement != nil {
|
|
|
|
for _, c := range s.Spec.TaskTemplate.Placement.Constraints {
|
|
|
|
service.Placement.Constraints = append(service.Placement.Constraints, NewPlacementConstraint(c))
|
|
|
|
}
|
|
|
|
for _, p := range s.Spec.TaskTemplate.Placement.Preferences {
|
|
|
|
service.Placement.Preferences = append(service.Placement.Preferences, p.Spread.SpreadDescriptor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LogDriver
|
|
|
|
if s.Spec.TaskTemplate.LogDriver != nil {
|
|
|
|
service.LogDriver.Name = s.Spec.TaskTemplate.LogDriver.Name
|
|
|
|
service.LogDriver.Options = mapToOptions(s.Spec.TaskTemplate.LogDriver.Options)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DNS
|
|
|
|
if s.Spec.TaskTemplate.ContainerSpec.DNSConfig != nil {
|
|
|
|
service.DNS.Servers = s.Spec.TaskTemplate.ContainerSpec.DNSConfig.Nameservers
|
|
|
|
service.DNS.Search = s.Spec.TaskTemplate.ContainerSpec.DNSConfig.Search
|
|
|
|
service.DNS.Options = s.Spec.TaskTemplate.ContainerSpec.DNSConfig.Options
|
|
|
|
}
|
|
|
|
return service
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Service) MergeTo(spec *swarm.ServiceSpec) (err error) {
|
|
|
|
spec.Name = s.Name
|
|
|
|
spec.Labels = toMap(s.Labels)
|
|
|
|
spec.TaskTemplate.ContainerSpec.Image = s.Image
|
|
|
|
spec.TaskTemplate.ContainerSpec.Dir = s.Dir
|
|
|
|
spec.TaskTemplate.ContainerSpec.User = s.User
|
|
|
|
spec.TaskTemplate.ContainerSpec.Hostname = s.Hostname
|
|
|
|
spec.TaskTemplate.ContainerSpec.Hosts = s.Hosts
|
|
|
|
spec.TaskTemplate.ContainerSpec.Labels = toMap(s.ContainerLabels)
|
|
|
|
spec.TaskTemplate.ContainerSpec.Env = toEnv(s.Env)
|
|
|
|
spec.TaskTemplate.ContainerSpec.Command = parseArgs(s.Command)
|
|
|
|
spec.TaskTemplate.ContainerSpec.Args = parseArgs(s.Args)
|
|
|
|
|
|
|
|
// Networks
|
|
|
|
spec.TaskTemplate.Networks = nil
|
|
|
|
for _, n := range s.Networks {
|
|
|
|
spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: n})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Endpoint
|
|
|
|
if s.Endpoint.Mode == "" && len(s.Endpoint.Ports) == 0 {
|
|
|
|
spec.EndpointSpec = nil
|
|
|
|
} else {
|
|
|
|
spec.EndpointSpec = &swarm.EndpointSpec{Mode: s.Endpoint.Mode}
|
|
|
|
for _, p := range s.Endpoint.Ports {
|
|
|
|
spec.EndpointSpec.Ports = append(spec.EndpointSpec.Ports, swarm.PortConfig{
|
|
|
|
Name: p.Name,
|
|
|
|
Protocol: p.Protocol,
|
|
|
|
TargetPort: p.TargetPort,
|
|
|
|
PublishedPort: p.PublishedPort,
|
|
|
|
PublishMode: p.PublishMode,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mounts
|
|
|
|
spec.TaskTemplate.ContainerSpec.Mounts = nil
|
|
|
|
for _, m := range s.Mounts {
|
|
|
|
spec.TaskTemplate.ContainerSpec.Mounts = append(spec.TaskTemplate.ContainerSpec.Mounts, mount.Mount{
|
|
|
|
Type: m.Type,
|
|
|
|
Source: m.Source,
|
|
|
|
Target: m.Target,
|
|
|
|
ReadOnly: m.Readonly,
|
|
|
|
Consistency: m.Consistency,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configs
|
|
|
|
spec.TaskTemplate.ContainerSpec.Configs = nil
|
|
|
|
for _, f := range s.Configs {
|
|
|
|
spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, f.ToConfig())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Secrets
|
|
|
|
spec.TaskTemplate.ContainerSpec.Secrets = nil
|
|
|
|
for _, f := range s.Secrets {
|
|
|
|
spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, f.ToSecret())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resource
|
|
|
|
if s.Resource.Limit.IsSet() || s.Resource.Reserve.IsSet() {
|
|
|
|
spec.TaskTemplate.Resources = &swarm.ResourceRequirements{}
|
|
|
|
if s.Resource.Limit.IsSet() {
|
|
|
|
spec.TaskTemplate.Resources.Limits, err = s.Resource.Limit.ToLimit()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if s.Resource.Limit.IsSet() {
|
|
|
|
spec.TaskTemplate.Resources.Reservations, err = s.Resource.Reserve.ToResources()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
spec.TaskTemplate.Resources = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Placement
|
|
|
|
if len(s.Placement.Constraints) == 0 && len(s.Placement.Preferences) == 0 {
|
|
|
|
spec.TaskTemplate.Placement = nil
|
|
|
|
} else {
|
|
|
|
spec.TaskTemplate.Placement = &swarm.Placement{}
|
|
|
|
for _, c := range s.Placement.Constraints {
|
|
|
|
if cons := c.ToConstraint(); cons != "" {
|
|
|
|
spec.TaskTemplate.Placement.Constraints = append(spec.TaskTemplate.Placement.Constraints, cons)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, p := range s.Placement.Preferences {
|
|
|
|
if p != "" {
|
|
|
|
pref := swarm.PlacementPreference{Spread: &swarm.SpreadOver{SpreadDescriptor: p}}
|
|
|
|
spec.TaskTemplate.Placement.Preferences = append(spec.TaskTemplate.Placement.Preferences, pref)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Schedule
|
|
|
|
spec.UpdateConfig = s.UpdatePolicy.Convert()
|
|
|
|
spec.RollbackConfig = s.RollbackPolicy.Convert()
|
|
|
|
spec.TaskTemplate.RestartPolicy = s.RestartPolicy.Convert()
|
|
|
|
|
|
|
|
// LogDriver
|
|
|
|
if s.LogDriver.Name == "" {
|
|
|
|
spec.TaskTemplate.LogDriver = nil
|
|
|
|
} else {
|
|
|
|
spec.TaskTemplate.LogDriver = &swarm.Driver{
|
|
|
|
Name: s.LogDriver.Name,
|
|
|
|
Options: toMap(s.LogDriver.Options),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Host & DNS
|
|
|
|
if len(s.DNS.Options) == 0 && len(s.DNS.Options) == 0 && len(s.DNS.Options) == 0 {
|
|
|
|
spec.TaskTemplate.ContainerSpec.DNSConfig = nil
|
|
|
|
} else {
|
|
|
|
spec.TaskTemplate.ContainerSpec.DNSConfig = &swarm.DNSConfig{
|
|
|
|
Nameservers: s.DNS.Servers,
|
|
|
|
Search: s.DNS.Search,
|
|
|
|
Options: s.DNS.Options,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|