swirl/model/docker.go

709 lines
20 KiB
Go

package model
import (
"encoding/base64"
"encoding/json"
"fmt"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/cuigh/auxo/byte/size"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
)
type Registry struct {
ID string `bson:"_id" json:"id,omitempty"`
Name string `bson:"name" json:"name,omitempty" valid:"required"`
URL string `bson:"url" json:"url,omitempty" valid:"required,url"`
Username string `bson:"username" json:"username,omitempty" valid:"required"`
Password string `bson:"password" json:"password,omitempty" valid:"required"`
CreatedAt time.Time `bson:"created_at" json:"created_at,omitempty"`
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
}
func (r *Registry) Match(image string) bool {
return strings.HasPrefix(image, r.URL)
}
func (r *Registry) GetEncodedAuth() string {
cfg := &types.AuthConfig{
ServerAddress: r.URL,
Username: r.Username,
Password: r.Password,
}
if buf, e := json.Marshal(cfg); e == nil {
return base64.URLEncoding.EncodeToString(buf)
}
return ""
}
type Option struct {
Name string `json:"name"`
Value string `json:"value"`
}
type Options []*Option
func NewOptions(m map[string]string) Options {
if len(m) == 0 {
return nil
}
opts := Options{}
for k, v := range m {
opts = append(opts, &Option{Name: k, Value: v})
}
sort.Slice(opts, func(i, j int) bool {
return opts[i].Name < opts[j].Name
})
return opts
}
func (opts Options) ToMap() map[string]string {
if len(opts) == 0 {
return nil
}
m := make(map[string]string)
for _, opt := range opts {
if opt != nil && opt.Name != "" && opt.Value != "" {
m[opt.Name] = opt.Value
}
}
return m
}
func (opts Options) Compress() Options {
if len(opts) > 0 {
var tmp Options
for _, opt := range opts {
if opt != nil {
tmp = append(tmp, opt)
}
}
return tmp
}
return opts
}
type ServiceListInfo struct {
Name string
Image string
Mode string
Actives uint64
Replicas uint64
Rollback bool
UpdatedAt time.Time
UpdateStatus string
}
func NewServiceListInfo(service swarm.Service, actives uint64) *ServiceListInfo {
info := &ServiceListInfo{
Name: service.Spec.Name,
Image: normalizeImage(service.Spec.TaskTemplate.ContainerSpec.Image),
Actives: actives,
UpdatedAt: service.UpdatedAt.Local(),
Rollback: service.PreviousSpec != nil,
}
if service.UpdateStatus != nil {
info.UpdateStatus = string(service.UpdateStatus.State)
}
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
info.Mode = "replicated"
info.Replicas = *service.Spec.Mode.Replicated.Replicas
} else if service.Spec.Mode.Global != nil {
info.Mode = "global"
// info.Replicas = tasksNoShutdown[service.ID]
}
return info
}
type ServiceDetailInfo struct {
swarm.Service
// Name string
// Image string
// Mode string
// Actives uint64
Replicas uint64
// UpdatedAt time.Time
Env map[string]string
Networks []Network
Command string
Args string
}
type Network struct {
ID string
Name string
Address string
}
func NewServiceDetailInfo(service swarm.Service) *ServiceDetailInfo {
info := &ServiceDetailInfo{
Service: service,
Command: strings.Join(service.Spec.TaskTemplate.ContainerSpec.Command, " "),
Args: strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "),
}
if service.Spec.Mode.Replicated != nil {
info.Replicas = *service.Spec.Mode.Replicated.Replicas
}
if len(service.Spec.TaskTemplate.ContainerSpec.Env) > 0 {
info.Env = make(map[string]string)
for _, env := range service.Spec.TaskTemplate.ContainerSpec.Env {
pair := strings.SplitN(env, "=", 2)
if len(pair) == 2 {
info.Env[pair[0]] = pair[1]
}
}
}
return info
}
type ServiceInfo struct {
Name string `json:"name"`
Version uint64 `json:"version"`
Registry string `json:"registry"`
RegistryURL string `json:"-"`
RegistryAuth string `json:"-"`
Image string `json:"image"`
Mode string `json:"mode"`
Replicas uint64 `json:"replicas"`
Command string `json:"command"`
Args string `json:"args"`
Dir string `json:"dir"`
User string `json:"user"`
Networks []string `json:"networks"`
Secrets []ConfigInfo `json:"secrets"`
Configs []ConfigInfo `json:"configs"`
Environments Options `json:"envs"`
ServiceLabels Options `json:"slabels"`
ContainerLabels Options `json:"clabels"`
Endpoint struct {
Mode swarm.ResolutionMode `json:"mode"`
Ports []EndpointPort `json:"ports"`
} `json:"endpoint"`
Mounts []Mount `json:"mounts"`
LogDriver struct {
Name string `json:"name"`
Options Options `json:"options"`
} `json:"log_driver"`
Placement struct {
Constraints []PlacementConstraint `json:"constraints"`
Preferences []PlacementPreference `json:"preferences"`
} `json:"placement"`
UpdatePolicy struct {
// Maximum number of tasks updated simultaneously (0 to update all at once), default 1.
Parallelism uint64 `json:"parallelism"`
// Amount of time between updates.
Delay string `json:"delay,omitempty"`
// FailureAction is the action to take when an update failures. (“pause”|“continue”|“rollback”) (default “pause”)
FailureAction string `json:"failure_action"`
// Update order (“start-first”|“stop-first”) (default “stop-first”)
Order string `json:"order"`
} `json:"update_policy"`
RollbackPolicy struct {
// Maximum number of tasks updated simultaneously (0 to update all at once), default 1.
Parallelism uint64 `json:"parallelism"`
// Amount of time between updates.
Delay string `json:"delay,omitempty"`
// FailureAction is the action to take when an update failures. (“pause”|“continue”) (default “pause”)
FailureAction string `json:"failure_action"`
// Update order (“start-first”|“stop-first”) (default “stop-first”)
Order string `json:"order"`
} `json:"rollback_policy"`
RestartPolicy struct {
// Restart when condition is met (“none”|“on-failure”|“any”) (default “any”)
Condition swarm.RestartPolicyCondition `json:"condition"`
// Maximum number of restarts before giving up
MaxAttempts uint64 `json:"max_attempts"`
// Delay between restart attempts
Delay string `json:"delay,omitempty"`
// Window used to evaluate the restart policy.
Window string `json:"window,omitempty"`
} `json:"restart_policy"`
Resource struct {
Limit ResourceInfo `json:"limit"`
Reserve ResourceInfo `json:"reserve"`
} `json:"resource"`
DNS struct {
Nameservers string `json:"nameservers,omitempty"`
Search string `json:"search,omitempty"`
Options string `json:"options,omitempty"`
} `json:"dns"`
Hostname string `json:"hostname"`
Hosts string `json:"hosts"`
}
func NewServiceInfo(service swarm.Service) *ServiceInfo {
spec := service.Spec
si := &ServiceInfo{
Name: spec.Name,
Version: service.Version.Index,
//Hostname: spec.TaskTemplate.ContainerSpec.Hostname,
Image: spec.TaskTemplate.ContainerSpec.Image,
Command: strings.Join(spec.TaskTemplate.ContainerSpec.Command, " "),
Args: strings.Join(spec.TaskTemplate.ContainerSpec.Args, " "),
Dir: spec.TaskTemplate.ContainerSpec.Dir,
User: spec.TaskTemplate.ContainerSpec.User,
ServiceLabels: NewOptions(spec.Labels),
ContainerLabels: NewOptions(spec.TaskTemplate.ContainerSpec.Labels),
}
for _, n := range service.Spec.TaskTemplate.Networks {
si.Networks = append(si.Networks, n.Target)
}
if spec.EndpointSpec != nil {
si.Endpoint.Mode = spec.EndpointSpec.Mode
for _, p := range spec.EndpointSpec.Ports {
si.Endpoint.Ports = append(si.Endpoint.Ports, EndpointPort{
Protocol: p.Protocol,
TargetPort: p.TargetPort,
PublishedPort: p.PublishedPort,
PublishMode: p.PublishMode,
})
}
}
if spec.Mode.Global != nil {
si.Mode = "global"
} else if spec.Mode.Replicated != nil {
si.Mode, si.Replicas = "replicated", *spec.Mode.Replicated.Replicas
}
if len(spec.TaskTemplate.ContainerSpec.Env) > 0 {
si.Environments = Options{}
for _, env := range spec.TaskTemplate.ContainerSpec.Env {
pair := strings.SplitN(env, "=", 2)
si.Environments = append(si.Environments, &Option{Name: pair[0], Value: pair[1]})
}
}
for _, m := range spec.TaskTemplate.ContainerSpec.Mounts {
mnt := Mount{
Type: m.Type,
Source: m.Source,
Target: m.Target,
ReadOnly: m.ReadOnly,
}
if m.BindOptions != nil {
mnt.Propagation = m.BindOptions.Propagation
}
si.Mounts = append(si.Mounts, mnt)
}
if spec.TaskTemplate.Resources != nil {
si.Resource.Limit = NewResourceInfo(spec.TaskTemplate.Resources.Limits)
si.Resource.Reserve = NewResourceInfo(spec.TaskTemplate.Resources.Reservations)
}
if spec.TaskTemplate.LogDriver != nil {
si.LogDriver.Name = spec.TaskTemplate.LogDriver.Name
si.LogDriver.Options = NewOptions(spec.TaskTemplate.LogDriver.Options)
}
if spec.TaskTemplate.Placement != nil {
for _, c := range spec.TaskTemplate.Placement.Constraints {
si.Placement.Constraints = append(si.Placement.Constraints, NewPlacementConstraint(c))
}
for _, p := range spec.TaskTemplate.Placement.Preferences {
si.Placement.Preferences = append(si.Placement.Preferences, PlacementPreference{Spread: p.Spread.SpreadDescriptor})
}
}
if spec.UpdateConfig != nil {
si.UpdatePolicy.Parallelism = spec.UpdateConfig.Parallelism
if spec.UpdateConfig.Delay > 0 {
si.UpdatePolicy.Delay = spec.UpdateConfig.Delay.String()
}
si.UpdatePolicy.FailureAction = spec.UpdateConfig.FailureAction
si.UpdatePolicy.Order = spec.UpdateConfig.Order
}
if spec.RollbackConfig != nil {
si.RollbackPolicy.Parallelism = spec.RollbackConfig.Parallelism
if spec.RollbackConfig.Delay > 0 {
si.RollbackPolicy.Delay = spec.RollbackConfig.Delay.String()
}
si.RollbackPolicy.FailureAction = spec.RollbackConfig.FailureAction
si.RollbackPolicy.Order = spec.RollbackConfig.Order
}
if spec.TaskTemplate.RestartPolicy != nil {
si.RestartPolicy.Condition = spec.TaskTemplate.RestartPolicy.Condition
if spec.TaskTemplate.RestartPolicy.MaxAttempts != nil {
si.RestartPolicy.MaxAttempts = *spec.TaskTemplate.RestartPolicy.MaxAttempts
}
if spec.TaskTemplate.RestartPolicy.Delay != nil && *spec.TaskTemplate.RestartPolicy.Delay > 0 {
si.RestartPolicy.Delay = spec.TaskTemplate.RestartPolicy.Delay.String()
}
if spec.TaskTemplate.RestartPolicy.Window != nil && *spec.TaskTemplate.RestartPolicy.Window > 0 {
si.RestartPolicy.Window = spec.TaskTemplate.RestartPolicy.Window.String()
}
}
for _, s := range spec.TaskTemplate.ContainerSpec.Secrets {
secret := NewSecretInfo(s)
si.Secrets = append(si.Secrets, secret)
}
for _, c := range spec.TaskTemplate.ContainerSpec.Configs {
config := NewConfigInfo(c)
si.Configs = append(si.Configs, config)
}
if dns := spec.TaskTemplate.ContainerSpec.DNSConfig; dns != nil {
si.DNS.Nameservers = strings.Join(dns.Nameservers, ",")
si.DNS.Search = strings.Join(dns.Search, ",")
si.DNS.Options = strings.Join(dns.Options, ",")
}
si.Hostname = spec.TaskTemplate.ContainerSpec.Hostname
if len(spec.TaskTemplate.ContainerSpec.Hosts) > 0 {
si.Hosts = strings.Join(spec.TaskTemplate.ContainerSpec.Hosts, "\n")
}
return si
}
// TODO: finish this method
func (si *ServiceInfo) ToServiceSpec() swarm.ServiceSpec {
return swarm.ServiceSpec{}
}
func (si *ServiceInfo) Normalize() {
si.Environments = si.Environments.Compress()
si.ServiceLabels = si.ServiceLabels.Compress()
si.ContainerLabels = si.ContainerLabels.Compress()
si.LogDriver.Options = si.LogDriver.Options.Compress()
if len(si.Configs) > 0 {
var tmp []ConfigInfo
for _, c := range si.Configs {
if c.ID != "" {
tmp = append(tmp, c)
}
}
si.Configs = tmp
}
if len(si.Secrets) > 0 {
var tmp []ConfigInfo
for _, c := range si.Secrets {
if c.ID != "" {
tmp = append(tmp, c)
}
}
si.Secrets = tmp
}
}
func (si *ServiceInfo) GetDNSConfig() *swarm.DNSConfig {
if si.DNS.Nameservers == "" && si.DNS.Search == "" && si.DNS.Options == "" {
return nil
}
c := &swarm.DNSConfig{}
if si.DNS.Nameservers != "" {
c.Nameservers = strings.Split(si.DNS.Nameservers, ",")
}
if si.DNS.Search != "" {
c.Search = strings.Split(si.DNS.Search, ",")
}
if si.DNS.Options != "" {
c.Options = strings.Split(si.DNS.Options, ",")
}
return c
}
type EndpointPort struct {
Name string `json:"name"`
Protocol swarm.PortConfigProtocol `json:"protocol"`
// TargetPort is the port inside the container
TargetPort uint32 `json:"target_port"`
// PublishedPort is the port on the swarm hosts
PublishedPort uint32 `json:"published_port"`
// PublishMode is the mode in which port is published
PublishMode swarm.PortConfigPublishMode `json:"publish_mode"`
}
type Mount struct {
Type mount.Type `json:"type"`
Source string `json:"src"`
Target string `json:"dst"`
ReadOnly bool `json:"read_only"`
Propagation mount.Propagation `json:"propagation"`
}
type ResourceInfo struct {
CPU float64 `json:"cpu"`
Memory string `json:"memory"`
}
func NewResourceInfo(res *swarm.Resources) ResourceInfo {
ri := ResourceInfo{}
if res != nil {
ri.CPU = float64(res.NanoCPUs) / 1e9
if res.MemoryBytes > 0 {
ri.Memory = size.Size(res.MemoryBytes).String()
}
}
return ri
}
func (r ResourceInfo) IsSet() bool {
return r.CPU > 0 || r.Memory != ""
}
func (r ResourceInfo) 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
}
type ConfigInfo struct {
ID string `json:"id"`
Name string `json:"name"`
FileName string `json:"file_name"`
UID string `json:"uid"`
GID string `json:"gid"`
Mode uint32 `json:"mode"`
}
func NewSecretInfo(ref *swarm.SecretReference) ConfigInfo {
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(ref.File.Mode), 8), 10, 32)
return ConfigInfo{
ID: ref.SecretID,
Name: ref.SecretName,
FileName: ref.File.Name,
UID: ref.File.UID,
GID: ref.File.GID,
Mode: uint32(mode),
}
}
func NewConfigInfo(ref *swarm.ConfigReference) ConfigInfo {
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(ref.File.Mode), 8), 10, 32)
return ConfigInfo{
ID: ref.ConfigID,
Name: ref.ConfigName,
FileName: ref.File.Name,
UID: ref.File.UID,
GID: ref.File.GID,
Mode: uint32(mode),
}
}
func (ci ConfigInfo) ToSecret() *swarm.SecretReference {
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(ci.Mode), 10), 8, 32)
return &swarm.SecretReference{
SecretID: ci.ID,
SecretName: ci.Name,
File: &swarm.SecretReferenceFileTarget{
Name: ci.FileName,
UID: ci.UID,
GID: ci.GID,
Mode: os.FileMode(mode),
},
}
}
func (ci ConfigInfo) ToConfig() *swarm.ConfigReference {
mode, _ := strconv.ParseUint(strconv.FormatUint(uint64(ci.Mode), 10), 8, 32)
return &swarm.ConfigReference{
ConfigID: ci.ID,
ConfigName: ci.Name,
File: &swarm.ConfigReferenceFileTarget{
Name: ci.FileName,
UID: ci.UID,
GID: ci.GID,
Mode: os.FileMode(mode),
},
}
}
type PlacementPreference struct {
Spread string `json:"spread"`
}
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 Driver struct {
Name string `json:"name,omitempty"`
Options map[string]string `json:"options,omitempty"`
}
type ConfigCreateInfo struct {
Name string `json:"name"`
Data string `json:"data"`
Labels Options `json:"labels"`
Template Driver `json:"template"`
Base64 bool `json:"base64"`
}
type ConfigUpdateInfo struct {
ID string `json:"id"`
Version uint64 `json:"version"`
ConfigCreateInfo
}
type TaskListArgs struct {
Service string `bind:"service"`
Node string `bind:"node"`
Name string `bind:"name"`
State string `bind:"state"`
PageIndex int `bind:"page"`
PageSize int `bind:"size"`
}
type TaskInfo struct {
swarm.Task
NodeName string
Image string
}
func NewTaskInfo(task swarm.Task, nodeName string) *TaskInfo {
return &TaskInfo{
Task: task,
NodeName: nodeName,
Image: normalizeImage(task.Spec.ContainerSpec.Image),
}
}
type NodeListInfo struct {
ID string
Name string
Role swarm.NodeRole
Version string
CPU int64
Memory float32
Address string
Status swarm.NodeState
Leader bool
}
func NewNodeListInfo(node swarm.Node) *NodeListInfo {
info := &NodeListInfo{
ID: node.ID,
Name: node.Spec.Name,
Role: node.Spec.Role,
Version: node.Description.Engine.EngineVersion,
CPU: node.Description.Resources.NanoCPUs / 1e9,
Memory: float32(node.Description.Resources.MemoryBytes>>20) / 1024,
Address: node.Status.Addr,
Status: node.Status.State,
Leader: node.ManagerStatus != nil && node.ManagerStatus.Leader,
}
if info.Name == "" {
info.Name = node.Description.Hostname
}
return info
}
type NodeUpdateInfo struct {
Version uint64 `json:"version"`
Name string `json:"name"`
Role swarm.NodeRole `json:"role"`
Availability swarm.NodeAvailability `json:"availability"`
Labels Options `json:"labels"`
}
type NetworkCreateInfo struct {
Name string `json:"name"`
Driver string `json:"driver"`
CustomDriver string `json:"custom_driver"`
Internal bool `json:"internal"`
Attachable bool
IPV4 struct {
Subnet string `json:"subnet"`
Gateway string `json:"gateway"`
IPRange string `json:"ip_range"`
} `json:"ipv4"`
IPV6 struct {
Enabled bool `json:"enabled"`
Subnet string `json:"subnet"`
Gateway string `json:"gateway"`
} `json:"ipv6"`
Options Options `json:"options"`
Labels Options `json:"labels"`
LogDriver struct {
Name string `json:"name"`
Options Options `json:"options"`
} `json:"log_driver"`
}
type VolumeCreateInfo struct {
Name string `json:"name" valid:"required"`
Driver string `json:"driver"`
CustomDriver string `json:"custom_driver"`
Options Options `json:"options"`
Labels Options `json:"labels"`
}
type ImageListInfo struct {
types.ImageSummary
CreatedAt time.Time
}
func NewImageListInfo(image types.ImageSummary) *ImageListInfo {
info := &ImageListInfo{
ImageSummary: image,
CreatedAt: time.Unix(image.Created, 0),
}
return info
}
type ContainerListArgs struct {
// created|restarting|running|removing|paused|exited|dead
Filter string `bind:"filter"`
Name string `bind:"name"`
PageIndex int `bind:"page"`
PageSize int `bind:"size"`
}
type ContainerListInfo struct {
types.Container
CreatedAt time.Time
}
func NewContainerListInfo(container types.Container) *ContainerListInfo {
info := &ContainerListInfo{
Container: container,
CreatedAt: time.Unix(container.Created, 0),
}
return info
}
func normalizeImage(image string) string {
// remove hash added by docker
if i := strings.Index(image, "@sha256:"); i > 0 {
image = image[:i]
}
return image
}