1/*
2   Copyright 2020 The Compose Specification Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package types
18
19import (
20	"encoding/json"
21	"fmt"
22	"sort"
23	"strings"
24	"time"
25
26	"github.com/docker/go-connections/nat"
27)
28
29// Duration is a thin wrapper around time.Duration with improved JSON marshalling
30type Duration time.Duration
31
32func (d Duration) String() string {
33	return time.Duration(d).String()
34}
35
36// ConvertDurationPtr converts a typedefined Duration pointer to a time.Duration pointer with the same value.
37func ConvertDurationPtr(d *Duration) *time.Duration {
38	if d == nil {
39		return nil
40	}
41	res := time.Duration(*d)
42	return &res
43}
44
45// MarshalJSON makes Duration implement json.Marshaler
46func (d Duration) MarshalJSON() ([]byte, error) {
47	return json.Marshal(d.String())
48}
49
50// MarshalYAML makes Duration implement yaml.Marshaler
51func (d Duration) MarshalYAML() (interface{}, error) {
52	return d.String(), nil
53}
54
55func (d *Duration) UnmarshalJSON(b []byte) error {
56	s := strings.Trim(string(b), "\"")
57	timeDuration, err := time.ParseDuration(s)
58	if err != nil {
59		return err
60	}
61	*d = Duration(timeDuration)
62	return nil
63}
64
65// Services is a list of ServiceConfig
66type Services []ServiceConfig
67
68// MarshalYAML makes Services implement yaml.Marshaller
69func (s Services) MarshalYAML() (interface{}, error) {
70	services := map[string]ServiceConfig{}
71	for _, service := range s {
72		services[service.Name] = service
73	}
74	return services, nil
75}
76
77// MarshalJSON makes Services implement json.Marshaler
78func (s Services) MarshalJSON() ([]byte, error) {
79	data, err := s.MarshalYAML()
80	if err != nil {
81		return nil, err
82	}
83	return json.MarshalIndent(data, "", "  ")
84}
85
86// ServiceConfig is the configuration of one service
87type ServiceConfig struct {
88	Name     string   `yaml:"-" json:"-"`
89	Profiles []string `mapstructure:"profiles" yaml:"profiles,omitempty" json:"profiles,omitempty"`
90
91	Build           *BuildConfig                     `yaml:",omitempty" json:"build,omitempty"`
92	BlkioConfig     *BlkioConfig                     `yaml:",omitempty" json:"blkio_config,omitempty"`
93	CapAdd          []string                         `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
94	CapDrop         []string                         `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"`
95	CgroupParent    string                           `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"`
96	CPUCount        int64                            `mapstructure:"cpu_count" yaml:"cpu_count,omitempty" json:"cpu_count,omitempty"`
97	CPUPercent      float32                          `mapstructure:"cpu_percent" yaml:"cpu_percent,omitempty" json:"cpu_percent,omitempty"`
98	CPUPeriod       int64                            `mapstructure:"cpu_period" yaml:"cpu_period,omitempty" json:"cpu_period,omitempty"`
99	CPUQuota        int64                            `mapstructure:"cpu_quota" yaml:"cpu_quota,omitempty" json:"cpu_quota,omitempty"`
100	CPURTPeriod     int64                            `mapstructure:"cpu_rt_period" yaml:"cpu_rt_period,omitempty" json:"cpu_rt_period,omitempty"`
101	CPURTRuntime    int64                            `mapstructure:"cpu_rt_runtime" yaml:"cpu_rt_runtime,omitempty" json:"cpu_rt_runtime,omitempty"`
102	CPUS            float32                          `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
103	CPUSet          string                           `mapstructure:"cpuset" yaml:"cpuset,omitempty" json:"cpuset,omitempty"`
104	CPUShares       int64                            `mapstructure:"cpu_shares" yaml:"cpu_shares,omitempty" json:"cpu_shares,omitempty"`
105	Command         ShellCommand                     `yaml:",omitempty" json:"command,omitempty"`
106	Configs         []ServiceConfigObjConfig         `yaml:",omitempty" json:"configs,omitempty"`
107	ContainerName   string                           `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"`
108	CredentialSpec  *CredentialSpecConfig            `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
109	DependsOn       DependsOnConfig                  `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
110	Deploy          *DeployConfig                    `yaml:",omitempty" json:"deploy,omitempty"`
111	Devices         []string                         `yaml:",omitempty" json:"devices,omitempty"`
112	DNS             StringList                       `yaml:",omitempty" json:"dns,omitempty"`
113	DNSOpts         []string                         `mapstructure:"dns_opt" yaml:"dns_opt,omitempty" json:"dns_opt,omitempty"`
114	DNSSearch       StringList                       `mapstructure:"dns_search" yaml:"dns_search,omitempty" json:"dns_search,omitempty"`
115	Dockerfile      string                           `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
116	DomainName      string                           `mapstructure:"domainname" yaml:"domainname,omitempty" json:"domainname,omitempty"`
117	Entrypoint      ShellCommand                     `yaml:",omitempty" json:"entrypoint,omitempty"`
118	Environment     MappingWithEquals                `yaml:",omitempty" json:"environment,omitempty"`
119	EnvFile         StringList                       `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"`
120	Expose          StringOrNumberList               `yaml:",omitempty" json:"expose,omitempty"`
121	Extends         ExtendsConfig                    `yaml:"extends,omitempty" json:"extends,omitempty"`
122	ExternalLinks   []string                         `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"`
123	ExtraHosts      HostsList                        `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
124	GroupAdd        []string                         `mapstructure:"group_app" yaml:"group_add,omitempty" json:"group_add,omitempty"`
125	Hostname        string                           `yaml:",omitempty" json:"hostname,omitempty"`
126	HealthCheck     *HealthCheckConfig               `yaml:",omitempty" json:"healthcheck,omitempty"`
127	Image           string                           `yaml:",omitempty" json:"image,omitempty"`
128	Init            *bool                            `yaml:",omitempty" json:"init,omitempty"`
129	Ipc             string                           `yaml:",omitempty" json:"ipc,omitempty"`
130	Isolation       string                           `mapstructure:"isolation" yaml:"isolation,omitempty" json:"isolation,omitempty"`
131	Labels          Labels                           `yaml:",omitempty" json:"labels,omitempty"`
132	Links           []string                         `yaml:",omitempty" json:"links,omitempty"`
133	Logging         *LoggingConfig                   `yaml:",omitempty" json:"logging,omitempty"`
134	LogDriver       string                           `mapstructure:"log_driver" yaml:"log_driver,omitempty" json:"log_driver,omitempty"`
135	LogOpt          map[string]string                `mapstructure:"log_opt" yaml:"log_opt,omitempty" json:"log_opt,omitempty"`
136	MemLimit        UnitBytes                        `mapstructure:"mem_limit" yaml:"mem_limit,omitempty" json:"mem_limit,omitempty"`
137	MemReservation  UnitBytes                        `mapstructure:"mem_reservation" yaml:"mem_reservation,omitempty" json:"mem_reservation,omitempty"`
138	MemSwapLimit    UnitBytes                        `mapstructure:"memswap_limit" yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"`
139	MemSwappiness   UnitBytes                        `mapstructure:"mem_swappiness" yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"`
140	MacAddress      string                           `mapstructure:"mac_address" yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
141	Net             string                           `yaml:"net,omitempty" json:"net,omitempty"`
142	NetworkMode     string                           `mapstructure:"network_mode" yaml:"network_mode,omitempty" json:"network_mode,omitempty"`
143	Networks        map[string]*ServiceNetworkConfig `yaml:",omitempty" json:"networks,omitempty"`
144	OomKillDisable  bool                             `mapstructure:"oom_kill_disable" yaml:"oom_kill_disable,omitempty" json:"oom_kill_disable,omitempty"`
145	OomScoreAdj     int64                            `mapstructure:"oom_score_adj" yaml:"oom_score_adj,omitempty" json:"oom_score_adj,omitempty"`
146	Pid             string                           `yaml:",omitempty" json:"pid,omitempty"`
147	PidsLimit       int64                            `mapstructure:"pids_limit" yaml:"pids_limit,omitempty" json:"pids_limit,omitempty"`
148	Platform        string                           `yaml:",omitempty" json:"platform,omitempty"`
149	Ports           []ServicePortConfig              `yaml:",omitempty" json:"ports,omitempty"`
150	Privileged      bool                             `yaml:",omitempty" json:"privileged,omitempty"`
151	PullPolicy      string                           `mapstructure:"pull_policy" yaml:"pull_policy,omitempty" json:"pull_policy,omitempty"`
152	ReadOnly        bool                             `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
153	Restart         string                           `yaml:",omitempty" json:"restart,omitempty"`
154	Runtime         string                           `yaml:",omitempty" json:"runtime,omitempty"`
155	Scale           int                              `yaml:"-" json:"-"`
156	Secrets         []ServiceSecretConfig            `yaml:",omitempty" json:"secrets,omitempty"`
157	SecurityOpt     []string                         `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"`
158	ShmSize         UnitBytes                        `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"`
159	StdinOpen       bool                             `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
160	StopGracePeriod *Duration                        `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
161	StopSignal      string                           `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
162	Sysctls         Mapping                          `yaml:",omitempty" json:"sysctls,omitempty"`
163	Tmpfs           StringList                       `yaml:",omitempty" json:"tmpfs,omitempty"`
164	Tty             bool                             `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"`
165	Ulimits         map[string]*UlimitsConfig        `yaml:",omitempty" json:"ulimits,omitempty"`
166	User            string                           `yaml:",omitempty" json:"user,omitempty"`
167	UserNSMode      string                           `mapstructure:"userns_mode" yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"`
168	Uts             string                           `yaml:"uts,omitempty" json:"uts,omitempty"`
169	VolumeDriver    string                           `mapstructure:"volume_driver" yaml:"volume_driver,omitempty" json:"volume_driver,omitempty"`
170	Volumes         []ServiceVolumeConfig            `yaml:",omitempty" json:"volumes,omitempty"`
171	VolumesFrom     []string                         `mapstructure:"volumes_from" yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"`
172	WorkingDir      string                           `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
173
174	Extensions map[string]interface{} `yaml:",inline" json:"-"`
175}
176
177// NetworksByPriority return the service networks IDs sorted according to Priority
178func (s *ServiceConfig) NetworksByPriority() []string {
179	type key struct {
180		name     string
181		priority int
182	}
183	var keys []key
184	for k, v := range s.Networks {
185		priority := 0
186		if v != nil {
187			priority = v.Priority
188		}
189		keys = append(keys, key{
190			name:     k,
191			priority: priority,
192		})
193	}
194	sort.Slice(keys, func(i, j int) bool {
195		return keys[i].priority > keys[j].priority
196	})
197	var sorted []string
198	for _, k := range keys {
199		sorted = append(sorted, k.name)
200	}
201	return sorted
202}
203
204const (
205	//PullPolicyAlways always pull images
206	PullPolicyAlways = "always"
207	//PullPolicyNever never pull images
208	PullPolicyNever = "never"
209	//PullPolicyIfNotPresent pull missing images
210	PullPolicyIfNotPresent = "if_not_present"
211	//PullPolicyIfNotPresent pull missing images
212	PullPolicyMissing = "missing"
213	//PullPolicyBuild force building images
214	PullPolicyBuild = "build"
215)
216
217const (
218	//RestartPolicyAlways always restart the container if it stops
219	RestartPolicyAlways = "always"
220	//RestartPolicyOnFailure restart the container if it exits due to an error
221	RestartPolicyOnFailure = "on-failure"
222	//RestartPolicyNo do not automatically restart the container
223	RestartPolicyNo = "no"
224	//RestartPolicyUnlessStopped always restart the container unless the container is stopped (manually or otherwise)
225	RestartPolicyUnlessStopped = "unless-stopped"
226)
227
228const (
229	// ServicePrefix is the prefix for references pointing to a service
230	ServicePrefix = "service:"
231	// ContainerPrefix is the prefix for references pointing to a container
232	ContainerPrefix = "container:"
233
234	// NetworkModeServicePrefix is the prefix for network_mode pointing to a service
235	// Deprecated prefer ServicePrefix
236	NetworkModeServicePrefix = ServicePrefix
237	// NetworkModeContainerPrefix is the prefix for network_mode pointing to a container
238	// Deprecated prefer ContainerPrefix
239	NetworkModeContainerPrefix = ContainerPrefix
240)
241
242// GetDependencies retrieve all services this service depends on
243func (s ServiceConfig) GetDependencies() []string {
244	dependencies := make(set)
245	for dependency := range s.DependsOn {
246		dependencies.append(dependency)
247	}
248	for _, link := range s.Links {
249		parts := strings.Split(link, ":")
250		if len(parts) == 2 {
251			dependencies.append(parts[0])
252		} else {
253			dependencies.append(link)
254		}
255	}
256	if strings.HasPrefix(s.NetworkMode, ServicePrefix) {
257		dependencies.append(s.NetworkMode[len(ServicePrefix):])
258	}
259	if strings.HasPrefix(s.Ipc, ServicePrefix) {
260		dependencies.append(s.Ipc[len(ServicePrefix):])
261	}
262	if strings.HasPrefix(s.Pid, ServicePrefix) {
263		dependencies.append(s.Pid[len(ServicePrefix):])
264	}
265	for _, vol := range s.VolumesFrom {
266		if !strings.HasPrefix(s.Pid, ContainerPrefix) {
267			dependencies.append(vol)
268		}
269	}
270
271	return dependencies.toSlice()
272}
273
274type set map[string]struct{}
275
276func (s set) append(strings ...string) {
277	for _, str := range strings {
278		s[str] = struct{}{}
279	}
280}
281
282func (s set) toSlice() []string {
283	slice := make([]string, 0, len(s))
284	for v := range s {
285		slice = append(slice, v)
286	}
287	return slice
288}
289
290// BuildConfig is a type for build
291type BuildConfig struct {
292	Context    string            `yaml:",omitempty" json:"context,omitempty"`
293	Dockerfile string            `yaml:",omitempty" json:"dockerfile,omitempty"`
294	Args       MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
295	Labels     Labels            `yaml:",omitempty" json:"labels,omitempty"`
296	CacheFrom  StringList        `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
297	ExtraHosts HostsList         `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
298	Isolation  string            `yaml:",omitempty" json:"isolation,omitempty"`
299	Network    string            `yaml:",omitempty" json:"network,omitempty"`
300	Target     string            `yaml:",omitempty" json:"target,omitempty"`
301
302	Extensions map[string]interface{} `yaml:",inline" json:"-"`
303}
304
305// BlkioConfig define blkio config
306type BlkioConfig struct {
307	Weight          uint16           `yaml:",omitempty" json:"weight,omitempty"`
308	WeightDevice    []WeightDevice   `yaml:",omitempty" json:"weight_device,omitempty"`
309	DeviceReadBps   []ThrottleDevice `yaml:",omitempty" json:"device_read_bps,omitempty"`
310	DeviceReadIOps  []ThrottleDevice `yaml:",omitempty" json:"device_read_iops,omitempty"`
311	DeviceWriteBps  []ThrottleDevice `yaml:",omitempty" json:"device_write_bps,omitempty"`
312	DeviceWriteIOps []ThrottleDevice `yaml:",omitempty" json:"device_write_iops,omitempty"`
313
314	Extensions map[string]interface{} `yaml:",inline" json:"-"`
315}
316
317// WeightDevice is a structure that holds device:weight pair
318type WeightDevice struct {
319	Path   string
320	Weight uint16
321
322	Extensions map[string]interface{} `yaml:",inline" json:"-"`
323}
324
325// ThrottleDevice is a structure that holds device:rate_per_second pair
326type ThrottleDevice struct {
327	Path string
328	Rate uint64
329
330	Extensions map[string]interface{} `yaml:",inline" json:"-"`
331}
332
333// ShellCommand is a string or list of string args
334type ShellCommand []string
335
336// StringList is a type for fields that can be a string or list of strings
337type StringList []string
338
339// StringOrNumberList is a type for fields that can be a list of strings or
340// numbers
341type StringOrNumberList []string
342
343// MappingWithEquals is a mapping type that can be converted from a list of
344// key[=value] strings.
345// For the key with an empty value (`key=`), the mapped value is set to a pointer to `""`.
346// For the key without value (`key`), the mapped value is set to nil.
347type MappingWithEquals map[string]*string
348
349// NewMappingWithEquals build a new Mapping from a set of KEY=VALUE strings
350func NewMappingWithEquals(values []string) MappingWithEquals {
351	mapping := MappingWithEquals{}
352	for _, env := range values {
353		tokens := strings.SplitN(env, "=", 2)
354		if len(tokens) > 1 {
355			mapping[tokens[0]] = &tokens[1]
356		} else {
357			mapping[env] = nil
358		}
359	}
360	return mapping
361}
362
363// OverrideBy update MappingWithEquals with values from another MappingWithEquals
364func (e MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals {
365	for k, v := range other {
366		e[k] = v
367	}
368	return e
369}
370
371// Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`)
372func (e MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals {
373	for k, v := range e {
374		if v == nil {
375			if value, ok := lookupFn(k); ok {
376				e[k] = &value
377			}
378		}
379	}
380	return e
381}
382
383// RemoveEmpty excludes keys that are not associated with a value
384func (e MappingWithEquals) RemoveEmpty() MappingWithEquals {
385	for k, v := range e {
386		if v == nil {
387			delete(e, k)
388		}
389	}
390	return e
391}
392
393// Mapping is a mapping type that can be converted from a list of
394// key[=value] strings.
395// For the key with an empty value (`key=`), or key without value (`key`), the
396// mapped value is set to an empty string `""`.
397type Mapping map[string]string
398
399// NewMapping build a new Mapping from a set of KEY=VALUE strings
400func NewMapping(values []string) Mapping {
401	mapping := Mapping{}
402	for _, value := range values {
403		parts := strings.SplitN(value, "=", 2)
404		key := parts[0]
405		switch {
406		case len(parts) == 1:
407			mapping[key] = ""
408		default:
409			mapping[key] = parts[1]
410		}
411	}
412	return mapping
413}
414
415// Labels is a mapping type for labels
416type Labels map[string]string
417
418func (l Labels) Add(key, value string) Labels {
419	if l == nil {
420		l = Labels{}
421	}
422	l[key] = value
423	return l
424}
425
426// MappingWithColon is a mapping type that can be converted from a list of
427// 'key: value' strings
428type MappingWithColon map[string]string
429
430// HostsList is a list of colon-separated host-ip mappings
431type HostsList []string
432
433// LoggingConfig the logging configuration for a service
434type LoggingConfig struct {
435	Driver  string            `yaml:",omitempty" json:"driver,omitempty"`
436	Options map[string]string `yaml:",omitempty" json:"options,omitempty"`
437
438	Extensions map[string]interface{} `yaml:",inline" json:"-"`
439}
440
441// DeployConfig the deployment configuration for a service
442type DeployConfig struct {
443	Mode           string         `yaml:",omitempty" json:"mode,omitempty"`
444	Replicas       *uint64        `yaml:",omitempty" json:"replicas,omitempty"`
445	Labels         Labels         `yaml:",omitempty" json:"labels,omitempty"`
446	UpdateConfig   *UpdateConfig  `mapstructure:"update_config" yaml:"update_config,omitempty" json:"update_config,omitempty"`
447	RollbackConfig *UpdateConfig  `mapstructure:"rollback_config" yaml:"rollback_config,omitempty" json:"rollback_config,omitempty"`
448	Resources      Resources      `yaml:",omitempty" json:"resources,omitempty"`
449	RestartPolicy  *RestartPolicy `mapstructure:"restart_policy" yaml:"restart_policy,omitempty" json:"restart_policy,omitempty"`
450	Placement      Placement      `yaml:",omitempty" json:"placement,omitempty"`
451	EndpointMode   string         `mapstructure:"endpoint_mode" yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"`
452
453	Extensions map[string]interface{} `yaml:",inline" json:"-"`
454}
455
456// HealthCheckConfig the healthcheck configuration for a service
457type HealthCheckConfig struct {
458	Test        HealthCheckTest `yaml:",omitempty" json:"test,omitempty"`
459	Timeout     *Duration       `yaml:",omitempty" json:"timeout,omitempty"`
460	Interval    *Duration       `yaml:",omitempty" json:"interval,omitempty"`
461	Retries     *uint64         `yaml:",omitempty" json:"retries,omitempty"`
462	StartPeriod *Duration       `mapstructure:"start_period" yaml:"start_period,omitempty" json:"start_period,omitempty"`
463	Disable     bool            `yaml:",omitempty" json:"disable,omitempty"`
464
465	Extensions map[string]interface{} `yaml:",inline" json:"-"`
466}
467
468// HealthCheckTest is the command run to test the health of a service
469type HealthCheckTest []string
470
471// UpdateConfig the service update configuration
472type UpdateConfig struct {
473	Parallelism     *uint64  `yaml:",omitempty" json:"parallelism,omitempty"`
474	Delay           Duration `yaml:",omitempty" json:"delay,omitempty"`
475	FailureAction   string   `mapstructure:"failure_action" yaml:"failure_action,omitempty" json:"failure_action,omitempty"`
476	Monitor         Duration `yaml:",omitempty" json:"monitor,omitempty"`
477	MaxFailureRatio float32  `mapstructure:"max_failure_ratio" yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"`
478	Order           string   `yaml:",omitempty" json:"order,omitempty"`
479
480	Extensions map[string]interface{} `yaml:",inline" json:"-"`
481}
482
483// Resources the resource limits and reservations
484type Resources struct {
485	Limits       *Resource `yaml:",omitempty" json:"limits,omitempty"`
486	Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"`
487
488	Extensions map[string]interface{} `yaml:",inline" json:"-"`
489}
490
491// Resource is a resource to be limited or reserved
492type Resource struct {
493	// TODO: types to convert from units and ratios
494	NanoCPUs         string            `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
495	MemoryBytes      UnitBytes         `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"`
496	Devices          []DeviceRequest   `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"`
497	GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"`
498
499	Extensions map[string]interface{} `yaml:",inline" json:"-"`
500}
501
502type DeviceRequest struct {
503	Capabilities []string `mapstructure:"capabilities" yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
504	Driver       string   `mapstructure:"driver" yaml:"driver,omitempty" json:"driver,omitempty"`
505	Count        int64    `mapstructure:"count" yaml:"count,omitempty" json:"count,omitempty"`
506	IDs          []string `mapstructure:"device_ids" yaml:"device_ids,omitempty" json:"device_ids,omitempty"`
507}
508
509// GenericResource represents a "user defined" resource which can
510// only be an integer (e.g: SSD=3) for a service
511type GenericResource struct {
512	DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec" yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"`
513
514	Extensions map[string]interface{} `yaml:",inline" json:"-"`
515}
516
517// DiscreteGenericResource represents a "user defined" resource which is defined
518// as an integer
519// "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...)
520// Value is used to count the resource (SSD=5, HDD=3, ...)
521type DiscreteGenericResource struct {
522	Kind  string `json:"kind"`
523	Value int64  `json:"value"`
524
525	Extensions map[string]interface{} `yaml:",inline" json:"-"`
526}
527
528// UnitBytes is the bytes type
529type UnitBytes int64
530
531// MarshalYAML makes UnitBytes implement yaml.Marshaller
532func (u UnitBytes) MarshalYAML() (interface{}, error) {
533	return fmt.Sprintf("%d", u), nil
534}
535
536// MarshalJSON makes UnitBytes implement json.Marshaler
537func (u UnitBytes) MarshalJSON() ([]byte, error) {
538	return []byte(fmt.Sprintf(`"%d"`, u)), nil
539}
540
541// RestartPolicy the service restart policy
542type RestartPolicy struct {
543	Condition   string    `yaml:",omitempty" json:"condition,omitempty"`
544	Delay       *Duration `yaml:",omitempty" json:"delay,omitempty"`
545	MaxAttempts *uint64   `mapstructure:"max_attempts" yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"`
546	Window      *Duration `yaml:",omitempty" json:"window,omitempty"`
547
548	Extensions map[string]interface{} `yaml:",inline" json:"-"`
549}
550
551// Placement constraints for the service
552type Placement struct {
553	Constraints []string               `yaml:",omitempty" json:"constraints,omitempty"`
554	Preferences []PlacementPreferences `yaml:",omitempty" json:"preferences,omitempty"`
555	MaxReplicas uint64                 `mapstructure:"max_replicas_per_node" yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"`
556
557	Extensions map[string]interface{} `yaml:",inline" json:"-"`
558}
559
560// PlacementPreferences is the preferences for a service placement
561type PlacementPreferences struct {
562	Spread string `yaml:",omitempty" json:"spread,omitempty"`
563
564	Extensions map[string]interface{} `yaml:",inline" json:"-"`
565}
566
567// ServiceNetworkConfig is the network configuration for a service
568type ServiceNetworkConfig struct {
569	Priority    int      `yaml:",omitempty" json:"priotirt,omitempty"`
570	Aliases     []string `yaml:",omitempty" json:"aliases,omitempty"`
571	Ipv4Address string   `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
572	Ipv6Address string   `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
573
574	Extensions map[string]interface{} `yaml:",inline" json:"-"`
575}
576
577// ServicePortConfig is the port configuration for a service
578type ServicePortConfig struct {
579	Mode      string `yaml:",omitempty" json:"mode,omitempty"`
580	HostIP    string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
581	Target    uint32 `yaml:",omitempty" json:"target,omitempty"`
582	Published uint32 `yaml:",omitempty" json:"published,omitempty"`
583	Protocol  string `yaml:",omitempty" json:"protocol,omitempty"`
584
585	Extensions map[string]interface{} `yaml:",inline" json:"-"`
586}
587
588// ParsePortConfig parse short syntax for service port configuration
589func ParsePortConfig(value string) ([]ServicePortConfig, error) {
590	var portConfigs []ServicePortConfig
591	ports, portBindings, err := nat.ParsePortSpecs([]string{value})
592	if err != nil {
593		return nil, err
594	}
595	// We need to sort the key of the ports to make sure it is consistent
596	keys := []string{}
597	for port := range ports {
598		keys = append(keys, string(port))
599	}
600	sort.Strings(keys)
601
602	for _, key := range keys {
603		port := nat.Port(key)
604		converted, err := convertPortToPortConfig(port, portBindings)
605		if err != nil {
606			return nil, err
607		}
608		portConfigs = append(portConfigs, converted...)
609	}
610	return portConfigs, nil
611}
612
613func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
614	portConfigs := []ServicePortConfig{}
615	for _, binding := range portBindings[port] {
616		startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
617
618		if err != nil && binding.HostPort != "" {
619			return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
620		}
621
622		for i := startHostPort; i <= endHostPort; i++ {
623			portConfigs = append(portConfigs, ServicePortConfig{
624				HostIP:    binding.HostIP,
625				Protocol:  strings.ToLower(port.Proto()),
626				Target:    uint32(port.Int()),
627				Published: uint32(i),
628				Mode:      "ingress",
629			})
630		}
631	}
632	return portConfigs, nil
633}
634
635// ServiceVolumeConfig are references to a volume used by a service
636type ServiceVolumeConfig struct {
637	Type        string               `yaml:",omitempty" json:"type,omitempty"`
638	Source      string               `yaml:",omitempty" json:"source,omitempty"`
639	Target      string               `yaml:",omitempty" json:"target,omitempty"`
640	ReadOnly    bool                 `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
641	Consistency string               `yaml:",omitempty" json:"consistency,omitempty"`
642	Bind        *ServiceVolumeBind   `yaml:",omitempty" json:"bind,omitempty"`
643	Volume      *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"`
644	Tmpfs       *ServiceVolumeTmpfs  `yaml:",omitempty" json:"tmpfs,omitempty"`
645
646	Extensions map[string]interface{} `yaml:",inline" json:"-"`
647}
648
649const (
650	// TypeBind is the type for mounting host dir
651	VolumeTypeBind = "bind"
652	// TypeVolume is the type for remote storage volumes
653	VolumeTypeVolume = "volume"
654	// TypeTmpfs is the type for mounting tmpfs
655	VolumeTypeTmpfs = "tmpfs"
656	// TypeNamedPipe is the type for mounting Windows named pipes
657	VolumeTypeNamedPipe = "npipe"
658)
659
660// ServiceVolumeBind are options for a service volume of type bind
661type ServiceVolumeBind struct {
662	Propagation    string `yaml:",omitempty" json:"propagation,omitempty"`
663	CreateHostPath bool   `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"`
664
665	Extensions map[string]interface{} `yaml:",inline" json:"-"`
666}
667
668// Propagation represents the propagation of a mount.
669const (
670	// PropagationRPrivate RPRIVATE
671	PropagationRPrivate string = "rprivate"
672	// PropagationPrivate PRIVATE
673	PropagationPrivate string = "private"
674	// PropagationRShared RSHARED
675	PropagationRShared string = "rshared"
676	// PropagationShared SHARED
677	PropagationShared string = "shared"
678	// PropagationRSlave RSLAVE
679	PropagationRSlave string = "rslave"
680	// PropagationSlave SLAVE
681	PropagationSlave string = "slave"
682)
683
684// ServiceVolumeVolume are options for a service volume of type volume
685type ServiceVolumeVolume struct {
686	NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
687
688	Extensions map[string]interface{} `yaml:",inline" json:"-"`
689}
690
691// ServiceVolumeTmpfs are options for a service volume of type tmpfs
692type ServiceVolumeTmpfs struct {
693	Size UnitBytes `yaml:",omitempty" json:"size,omitempty"`
694
695	Extensions map[string]interface{} `yaml:",inline" json:"-"`
696}
697
698// FileReferenceConfig for a reference to a swarm file object
699type FileReferenceConfig struct {
700	Source string  `yaml:",omitempty" json:"source,omitempty"`
701	Target string  `yaml:",omitempty" json:"target,omitempty"`
702	UID    string  `yaml:",omitempty" json:"uid,omitempty"`
703	GID    string  `yaml:",omitempty" json:"gid,omitempty"`
704	Mode   *uint32 `yaml:",omitempty" json:"mode,omitempty"`
705
706	Extensions map[string]interface{} `yaml:",inline" json:"-"`
707}
708
709// ServiceConfigObjConfig is the config obj configuration for a service
710type ServiceConfigObjConfig FileReferenceConfig
711
712// ServiceSecretConfig is the secret configuration for a service
713type ServiceSecretConfig FileReferenceConfig
714
715// UlimitsConfig the ulimit configuration
716type UlimitsConfig struct {
717	Single int `yaml:",omitempty" json:"single,omitempty"`
718	Soft   int `yaml:",omitempty" json:"soft,omitempty"`
719	Hard   int `yaml:",omitempty" json:"hard,omitempty"`
720
721	Extensions map[string]interface{} `yaml:",inline" json:"-"`
722}
723
724// MarshalYAML makes UlimitsConfig implement yaml.Marshaller
725func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
726	if u.Single != 0 {
727		return u.Single, nil
728	}
729	return u, nil
730}
731
732// MarshalJSON makes UlimitsConfig implement json.Marshaller
733func (u *UlimitsConfig) MarshalJSON() ([]byte, error) {
734	if u.Single != 0 {
735		return json.Marshal(u.Single)
736	}
737	// Pass as a value to avoid re-entering this method and use the default implementation
738	return json.Marshal(*u)
739}
740
741// NetworkConfig for a network
742type NetworkConfig struct {
743	Name       string                 `yaml:",omitempty" json:"name,omitempty"`
744	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"`
745	DriverOpts map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
746	Ipam       IPAMConfig             `yaml:",omitempty" json:"ipam,omitempty"`
747	External   External               `yaml:",omitempty" json:"external,omitempty"`
748	Internal   bool                   `yaml:",omitempty" json:"internal,omitempty"`
749	Attachable bool                   `yaml:",omitempty" json:"attachable,omitempty"`
750	Labels     Labels                 `yaml:",omitempty" json:"labels,omitempty"`
751	Extensions map[string]interface{} `yaml:",inline" json:"-"`
752}
753
754// IPAMConfig for a network
755type IPAMConfig struct {
756	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"`
757	Config     []*IPAMPool            `yaml:",omitempty" json:"config,omitempty"`
758	Extensions map[string]interface{} `yaml:",inline" json:"-"`
759}
760
761// IPAMPool for a network
762type IPAMPool struct {
763	Subnet             string                 `yaml:",omitempty" json:"subnet,omitempty"`
764	Gateway            string                 `yaml:",omitempty" json:"gateway,omitempty"`
765	IPRange            string                 `mapstructure:"ip_range" yaml:"ip_range,omitempty" json:"ip_range,omitempty"`
766	AuxiliaryAddresses map[string]string      `mapstructure:"aux_addresses" yaml:"aux_addresses,omitempty" json:"aux_addresses,omitempty"`
767	Extensions         map[string]interface{} `yaml:",inline" json:"-"`
768}
769
770// VolumeConfig for a volume
771type VolumeConfig struct {
772	Name       string                 `yaml:",omitempty" json:"name,omitempty"`
773	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"`
774	DriverOpts map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
775	External   External               `yaml:",omitempty" json:"external,omitempty"`
776	Labels     Labels                 `yaml:",omitempty" json:"labels,omitempty"`
777	Extensions map[string]interface{} `yaml:",inline" json:"-"`
778}
779
780// External identifies a Volume or Network as a reference to a resource that is
781// not managed, and should already exist.
782// External.name is deprecated and replaced by Volume.name
783type External struct {
784	Name       string                 `yaml:",omitempty" json:"name,omitempty"`
785	External   bool                   `yaml:",omitempty" json:"external,omitempty"`
786	Extensions map[string]interface{} `yaml:",inline" json:"-"`
787}
788
789// MarshalYAML makes External implement yaml.Marshaller
790func (e External) MarshalYAML() (interface{}, error) {
791	if e.Name == "" {
792		return e.External, nil
793	}
794	return External{Name: e.Name}, nil
795}
796
797// MarshalJSON makes External implement json.Marshaller
798func (e External) MarshalJSON() ([]byte, error) {
799	if e.Name == "" {
800		return []byte(fmt.Sprintf("%v", e.External)), nil
801	}
802	return []byte(fmt.Sprintf(`{"name": %q}`, e.Name)), nil
803}
804
805// CredentialSpecConfig for credential spec on Windows
806type CredentialSpecConfig struct {
807	Config     string                 `yaml:",omitempty" json:"config,omitempty"` // Config was added in API v1.40
808	File       string                 `yaml:",omitempty" json:"file,omitempty"`
809	Registry   string                 `yaml:",omitempty" json:"registry,omitempty"`
810	Extensions map[string]interface{} `yaml:",inline" json:"-"`
811}
812
813// FileObjectConfig is a config type for a file used by a service
814type FileObjectConfig struct {
815	Name           string                 `yaml:",omitempty" json:"name,omitempty"`
816	File           string                 `yaml:",omitempty" json:"file,omitempty"`
817	External       External               `yaml:",omitempty" json:"external,omitempty"`
818	Labels         Labels                 `yaml:",omitempty" json:"labels,omitempty"`
819	Driver         string                 `yaml:",omitempty" json:"driver,omitempty"`
820	DriverOpts     map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
821	TemplateDriver string                 `mapstructure:"template_driver" yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
822	Extensions     map[string]interface{} `yaml:",inline" json:"-"`
823}
824
825const (
826	// ServiceConditionCompletedSuccessfully is the type for waiting until a service has completed successfully (exit code 0).
827	ServiceConditionCompletedSuccessfully = "service_completed_successfully"
828
829	// ServiceConditionHealthy is the type for waiting until a service is healthy.
830	ServiceConditionHealthy = "service_healthy"
831
832	// ServiceConditionStarted is the type for waiting until a service has started (default).
833	ServiceConditionStarted = "service_started"
834)
835
836type DependsOnConfig map[string]ServiceDependency
837
838type ServiceDependency struct {
839	Condition  string                 `yaml:",omitempty" json:"condition,omitempty"`
840	Extensions map[string]interface{} `yaml:",inline" json:"-"`
841}
842
843type ExtendsConfig MappingWithEquals
844
845// SecretConfig for a secret
846type SecretConfig FileObjectConfig
847
848// ConfigObjConfig is the config for the swarm "Config" object
849type ConfigObjConfig FileObjectConfig
850