1package convert // import "github.com/docker/docker/daemon/cluster/convert"
2
3import (
4	"fmt"
5	"strings"
6
7	types "github.com/docker/docker/api/types/swarm"
8	"github.com/docker/docker/api/types/swarm/runtime"
9	"github.com/docker/docker/pkg/namesgenerator"
10	swarmapi "github.com/docker/swarmkit/api"
11	"github.com/docker/swarmkit/api/genericresource"
12	"github.com/gogo/protobuf/proto"
13	gogotypes "github.com/gogo/protobuf/types"
14	"github.com/pkg/errors"
15)
16
17var (
18	// ErrUnsupportedRuntime returns an error if the runtime is not supported by the daemon
19	ErrUnsupportedRuntime = errors.New("unsupported runtime")
20	// ErrMismatchedRuntime returns an error if the runtime does not match the provided spec
21	ErrMismatchedRuntime = errors.New("mismatched Runtime and *Spec fields")
22)
23
24// ServiceFromGRPC converts a grpc Service to a Service.
25func ServiceFromGRPC(s swarmapi.Service) (types.Service, error) {
26	curSpec, err := serviceSpecFromGRPC(&s.Spec)
27	if err != nil {
28		return types.Service{}, err
29	}
30	prevSpec, err := serviceSpecFromGRPC(s.PreviousSpec)
31	if err != nil {
32		return types.Service{}, err
33	}
34	service := types.Service{
35		ID:           s.ID,
36		Spec:         *curSpec,
37		PreviousSpec: prevSpec,
38
39		Endpoint: endpointFromGRPC(s.Endpoint),
40	}
41
42	// Meta
43	service.Version.Index = s.Meta.Version.Index
44	service.CreatedAt, _ = gogotypes.TimestampFromProto(s.Meta.CreatedAt)
45	service.UpdatedAt, _ = gogotypes.TimestampFromProto(s.Meta.UpdatedAt)
46
47	if s.JobStatus != nil {
48		service.JobStatus = &types.JobStatus{
49			JobIteration: types.Version{
50				Index: s.JobStatus.JobIteration.Index,
51			},
52		}
53		service.JobStatus.LastExecution, _ = gogotypes.TimestampFromProto(s.JobStatus.LastExecution)
54	}
55
56	// UpdateStatus
57	if s.UpdateStatus != nil {
58		service.UpdateStatus = &types.UpdateStatus{}
59		switch s.UpdateStatus.State {
60		case swarmapi.UpdateStatus_UPDATING:
61			service.UpdateStatus.State = types.UpdateStateUpdating
62		case swarmapi.UpdateStatus_PAUSED:
63			service.UpdateStatus.State = types.UpdateStatePaused
64		case swarmapi.UpdateStatus_COMPLETED:
65			service.UpdateStatus.State = types.UpdateStateCompleted
66		case swarmapi.UpdateStatus_ROLLBACK_STARTED:
67			service.UpdateStatus.State = types.UpdateStateRollbackStarted
68		case swarmapi.UpdateStatus_ROLLBACK_PAUSED:
69			service.UpdateStatus.State = types.UpdateStateRollbackPaused
70		case swarmapi.UpdateStatus_ROLLBACK_COMPLETED:
71			service.UpdateStatus.State = types.UpdateStateRollbackCompleted
72		}
73
74		startedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.StartedAt)
75		if !startedAt.IsZero() && startedAt.Unix() != 0 {
76			service.UpdateStatus.StartedAt = &startedAt
77		}
78
79		completedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.CompletedAt)
80		if !completedAt.IsZero() && completedAt.Unix() != 0 {
81			service.UpdateStatus.CompletedAt = &completedAt
82		}
83
84		service.UpdateStatus.Message = s.UpdateStatus.Message
85	}
86
87	return service, nil
88}
89
90func serviceSpecFromGRPC(spec *swarmapi.ServiceSpec) (*types.ServiceSpec, error) {
91	if spec == nil {
92		return nil, nil
93	}
94
95	serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
96	for _, n := range spec.Networks {
97		netConfig := types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverOpts: n.DriverAttachmentOpts}
98		serviceNetworks = append(serviceNetworks, netConfig)
99
100	}
101
102	taskTemplate, err := taskSpecFromGRPC(spec.Task)
103	if err != nil {
104		return nil, err
105	}
106
107	switch t := spec.Task.GetRuntime().(type) {
108	case *swarmapi.TaskSpec_Container:
109		containerConfig := t.Container
110		taskTemplate.ContainerSpec = containerSpecFromGRPC(containerConfig)
111		taskTemplate.Runtime = types.RuntimeContainer
112	case *swarmapi.TaskSpec_Generic:
113		switch t.Generic.Kind {
114		case string(types.RuntimePlugin):
115			taskTemplate.Runtime = types.RuntimePlugin
116		default:
117			return nil, fmt.Errorf("unknown task runtime type: %s", t.Generic.Payload.TypeUrl)
118		}
119
120	default:
121		return nil, fmt.Errorf("error creating service; unsupported runtime %T", t)
122	}
123
124	convertedSpec := &types.ServiceSpec{
125		Annotations:  annotationsFromGRPC(spec.Annotations),
126		TaskTemplate: taskTemplate,
127		Networks:     serviceNetworks,
128		EndpointSpec: endpointSpecFromGRPC(spec.Endpoint),
129	}
130
131	// UpdateConfig
132	convertedSpec.UpdateConfig = updateConfigFromGRPC(spec.Update)
133	convertedSpec.RollbackConfig = updateConfigFromGRPC(spec.Rollback)
134
135	// Mode
136	switch t := spec.GetMode().(type) {
137	case *swarmapi.ServiceSpec_Global:
138		convertedSpec.Mode.Global = &types.GlobalService{}
139	case *swarmapi.ServiceSpec_Replicated:
140		convertedSpec.Mode.Replicated = &types.ReplicatedService{
141			Replicas: &t.Replicated.Replicas,
142		}
143	case *swarmapi.ServiceSpec_ReplicatedJob:
144		convertedSpec.Mode.ReplicatedJob = &types.ReplicatedJob{
145			MaxConcurrent:    &t.ReplicatedJob.MaxConcurrent,
146			TotalCompletions: &t.ReplicatedJob.TotalCompletions,
147		}
148	case *swarmapi.ServiceSpec_GlobalJob:
149		convertedSpec.Mode.GlobalJob = &types.GlobalJob{}
150	}
151
152	return convertedSpec, nil
153}
154
155// ServiceSpecToGRPC converts a ServiceSpec to a grpc ServiceSpec.
156func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
157	name := s.Name
158	if name == "" {
159		name = namesgenerator.GetRandomName(0)
160	}
161
162	serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
163	for _, n := range s.Networks {
164		netConfig := &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverAttachmentOpts: n.DriverOpts}
165		serviceNetworks = append(serviceNetworks, netConfig)
166	}
167
168	taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
169	for _, n := range s.TaskTemplate.Networks {
170		netConfig := &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverAttachmentOpts: n.DriverOpts}
171		taskNetworks = append(taskNetworks, netConfig)
172
173	}
174
175	spec := swarmapi.ServiceSpec{
176		Annotations: swarmapi.Annotations{
177			Name:   name,
178			Labels: s.Labels,
179		},
180		Task: swarmapi.TaskSpec{
181			Resources:   resourcesToGRPC(s.TaskTemplate.Resources),
182			LogDriver:   driverToGRPC(s.TaskTemplate.LogDriver),
183			Networks:    taskNetworks,
184			ForceUpdate: s.TaskTemplate.ForceUpdate,
185		},
186		Networks: serviceNetworks,
187	}
188
189	switch s.TaskTemplate.Runtime {
190	case types.RuntimeContainer, "": // if empty runtime default to container
191		if s.TaskTemplate.ContainerSpec != nil {
192			containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
193			if err != nil {
194				return swarmapi.ServiceSpec{}, err
195			}
196			if s.TaskTemplate.Resources != nil && s.TaskTemplate.Resources.Limits != nil {
197				// TODO remove this (or keep for backward compat) once SwarmKit API moved PidsLimit into Resources
198				containerSpec.PidsLimit = s.TaskTemplate.Resources.Limits.Pids
199			}
200			spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
201		} else {
202			// If the ContainerSpec is nil, we can't set the task runtime
203			return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
204		}
205	case types.RuntimePlugin:
206		if s.TaskTemplate.PluginSpec != nil {
207			if s.Mode.Replicated != nil {
208				return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
209			}
210
211			s.Mode.Global = &types.GlobalService{} // must always be global
212
213			pluginSpec, err := proto.Marshal(s.TaskTemplate.PluginSpec)
214			if err != nil {
215				return swarmapi.ServiceSpec{}, err
216			}
217			spec.Task.Runtime = &swarmapi.TaskSpec_Generic{
218				Generic: &swarmapi.GenericRuntimeSpec{
219					Kind: string(types.RuntimePlugin),
220					Payload: &gogotypes.Any{
221						TypeUrl: string(types.RuntimeURLPlugin),
222						Value:   pluginSpec,
223					},
224				},
225			}
226		} else {
227			return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
228		}
229	case types.RuntimeNetworkAttachment:
230		// NOTE(dperny) I'm leaving this case here for completeness. The actual
231		// code is left out deliberately, as we should refuse to parse a
232		// Network Attachment runtime; it will cause weird behavior all over
233		// the system if we do. Instead, fallthrough and return
234		// ErrUnsupportedRuntime if we get one.
235		fallthrough
236	default:
237		return swarmapi.ServiceSpec{}, ErrUnsupportedRuntime
238	}
239
240	restartPolicy, err := restartPolicyToGRPC(s.TaskTemplate.RestartPolicy)
241	if err != nil {
242		return swarmapi.ServiceSpec{}, err
243	}
244	spec.Task.Restart = restartPolicy
245
246	if s.TaskTemplate.Placement != nil {
247		var preferences []*swarmapi.PlacementPreference
248		for _, pref := range s.TaskTemplate.Placement.Preferences {
249			if pref.Spread != nil {
250				preferences = append(preferences, &swarmapi.PlacementPreference{
251					Preference: &swarmapi.PlacementPreference_Spread{
252						Spread: &swarmapi.SpreadOver{
253							SpreadDescriptor: pref.Spread.SpreadDescriptor,
254						},
255					},
256				})
257			}
258		}
259		var platforms []*swarmapi.Platform
260		for _, plat := range s.TaskTemplate.Placement.Platforms {
261			platforms = append(platforms, &swarmapi.Platform{
262				Architecture: plat.Architecture,
263				OS:           plat.OS,
264			})
265		}
266		spec.Task.Placement = &swarmapi.Placement{
267			Constraints: s.TaskTemplate.Placement.Constraints,
268			Preferences: preferences,
269			MaxReplicas: s.TaskTemplate.Placement.MaxReplicas,
270			Platforms:   platforms,
271		}
272	}
273
274	spec.Update, err = updateConfigToGRPC(s.UpdateConfig)
275	if err != nil {
276		return swarmapi.ServiceSpec{}, err
277	}
278	spec.Rollback, err = updateConfigToGRPC(s.RollbackConfig)
279	if err != nil {
280		return swarmapi.ServiceSpec{}, err
281	}
282
283	if s.EndpointSpec != nil {
284		if s.EndpointSpec.Mode != "" &&
285			s.EndpointSpec.Mode != types.ResolutionModeVIP &&
286			s.EndpointSpec.Mode != types.ResolutionModeDNSRR {
287			return swarmapi.ServiceSpec{}, fmt.Errorf("invalid resolution mode: %q", s.EndpointSpec.Mode)
288		}
289
290		spec.Endpoint = &swarmapi.EndpointSpec{}
291
292		spec.Endpoint.Mode = swarmapi.EndpointSpec_ResolutionMode(swarmapi.EndpointSpec_ResolutionMode_value[strings.ToUpper(string(s.EndpointSpec.Mode))])
293
294		for _, portConfig := range s.EndpointSpec.Ports {
295			spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
296				Name:          portConfig.Name,
297				Protocol:      swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
298				PublishMode:   swarmapi.PortConfig_PublishMode(swarmapi.PortConfig_PublishMode_value[strings.ToUpper(string(portConfig.PublishMode))]),
299				TargetPort:    portConfig.TargetPort,
300				PublishedPort: portConfig.PublishedPort,
301			})
302		}
303	}
304
305	// Mode
306	numModes := 0
307	if s.Mode.Global != nil {
308		numModes++
309	}
310	if s.Mode.Replicated != nil {
311		numModes++
312	}
313	if s.Mode.ReplicatedJob != nil {
314		numModes++
315	}
316	if s.Mode.GlobalJob != nil {
317		numModes++
318	}
319
320	if numModes > 1 {
321		return swarmapi.ServiceSpec{}, fmt.Errorf("must specify only one service mode")
322	}
323
324	if s.Mode.Global != nil {
325		spec.Mode = &swarmapi.ServiceSpec_Global{
326			Global: &swarmapi.GlobalService{},
327		}
328	} else if s.Mode.GlobalJob != nil {
329		spec.Mode = &swarmapi.ServiceSpec_GlobalJob{
330			GlobalJob: &swarmapi.GlobalJob{},
331		}
332	} else if s.Mode.ReplicatedJob != nil {
333		// if the service is a replicated job, we have two different kinds of
334		// values that might need to be defaulted.
335
336		r := &swarmapi.ReplicatedJob{}
337		if s.Mode.ReplicatedJob.MaxConcurrent != nil {
338			r.MaxConcurrent = *s.Mode.ReplicatedJob.MaxConcurrent
339		} else {
340			r.MaxConcurrent = 1
341		}
342
343		if s.Mode.ReplicatedJob.TotalCompletions != nil {
344			r.TotalCompletions = *s.Mode.ReplicatedJob.TotalCompletions
345		} else {
346			r.TotalCompletions = r.MaxConcurrent
347		}
348
349		spec.Mode = &swarmapi.ServiceSpec_ReplicatedJob{
350			ReplicatedJob: r,
351		}
352	} else if s.Mode.Replicated != nil && s.Mode.Replicated.Replicas != nil {
353		spec.Mode = &swarmapi.ServiceSpec_Replicated{
354			Replicated: &swarmapi.ReplicatedService{Replicas: *s.Mode.Replicated.Replicas},
355		}
356	} else {
357		spec.Mode = &swarmapi.ServiceSpec_Replicated{
358			Replicated: &swarmapi.ReplicatedService{Replicas: 1},
359		}
360	}
361
362	return spec, nil
363}
364
365func annotationsFromGRPC(ann swarmapi.Annotations) types.Annotations {
366	a := types.Annotations{
367		Name:   ann.Name,
368		Labels: ann.Labels,
369	}
370
371	if a.Labels == nil {
372		a.Labels = make(map[string]string)
373	}
374
375	return a
376}
377
378// GenericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
379func GenericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []types.GenericResource {
380	var generic []types.GenericResource
381	for _, res := range genericRes {
382		var current types.GenericResource
383
384		switch r := res.Resource.(type) {
385		case *swarmapi.GenericResource_DiscreteResourceSpec:
386			current.DiscreteResourceSpec = &types.DiscreteGenericResource{
387				Kind:  r.DiscreteResourceSpec.Kind,
388				Value: r.DiscreteResourceSpec.Value,
389			}
390		case *swarmapi.GenericResource_NamedResourceSpec:
391			current.NamedResourceSpec = &types.NamedGenericResource{
392				Kind:  r.NamedResourceSpec.Kind,
393				Value: r.NamedResourceSpec.Value,
394			}
395		}
396
397		generic = append(generic, current)
398	}
399
400	return generic
401}
402
403// resourcesFromGRPC creates a ResourceRequirements from the GRPC TaskSpec.
404// We currently require the whole TaskSpec to be passed, because PidsLimit
405// is returned as part of the container spec, instead of Resources
406// TODO move PidsLimit to Resources in the Swarm API
407func resourcesFromGRPC(ts *swarmapi.TaskSpec) *types.ResourceRequirements {
408	var resources *types.ResourceRequirements
409
410	if cs := ts.GetContainer(); cs != nil && cs.PidsLimit != 0 {
411		resources = &types.ResourceRequirements{
412			Limits: &types.Limit{
413				Pids: cs.PidsLimit,
414			},
415		}
416	}
417	if ts.Resources != nil {
418		if resources == nil {
419			resources = &types.ResourceRequirements{}
420		}
421		res := ts.Resources
422		if res.Limits != nil {
423			if resources.Limits == nil {
424				resources.Limits = &types.Limit{}
425			}
426			resources.Limits.NanoCPUs = res.Limits.NanoCPUs
427			resources.Limits.MemoryBytes = res.Limits.MemoryBytes
428		}
429		if res.Reservations != nil {
430			resources.Reservations = &types.Resources{
431				NanoCPUs:         res.Reservations.NanoCPUs,
432				MemoryBytes:      res.Reservations.MemoryBytes,
433				GenericResources: GenericResourcesFromGRPC(res.Reservations.Generic),
434			}
435		}
436	}
437
438	return resources
439}
440
441// GenericResourcesToGRPC converts a GenericResource to a GRPC GenericResource
442func GenericResourcesToGRPC(genericRes []types.GenericResource) []*swarmapi.GenericResource {
443	var generic []*swarmapi.GenericResource
444	for _, res := range genericRes {
445		var r *swarmapi.GenericResource
446
447		if res.DiscreteResourceSpec != nil {
448			r = genericresource.NewDiscrete(res.DiscreteResourceSpec.Kind, res.DiscreteResourceSpec.Value)
449		} else if res.NamedResourceSpec != nil {
450			r = genericresource.NewString(res.NamedResourceSpec.Kind, res.NamedResourceSpec.Value)
451		}
452
453		generic = append(generic, r)
454	}
455
456	return generic
457}
458
459func resourcesToGRPC(res *types.ResourceRequirements) *swarmapi.ResourceRequirements {
460	var reqs *swarmapi.ResourceRequirements
461	if res != nil {
462		reqs = &swarmapi.ResourceRequirements{}
463		if res.Limits != nil {
464			// TODO add PidsLimit once Swarm API has been updated to move it into Limits
465			reqs.Limits = &swarmapi.Resources{
466				NanoCPUs:    res.Limits.NanoCPUs,
467				MemoryBytes: res.Limits.MemoryBytes,
468			}
469		}
470		if res.Reservations != nil {
471			reqs.Reservations = &swarmapi.Resources{
472				NanoCPUs:    res.Reservations.NanoCPUs,
473				MemoryBytes: res.Reservations.MemoryBytes,
474				Generic:     GenericResourcesToGRPC(res.Reservations.GenericResources),
475			}
476
477		}
478	}
479	return reqs
480}
481
482func restartPolicyFromGRPC(p *swarmapi.RestartPolicy) *types.RestartPolicy {
483	var rp *types.RestartPolicy
484	if p != nil {
485		rp = &types.RestartPolicy{}
486
487		switch p.Condition {
488		case swarmapi.RestartOnNone:
489			rp.Condition = types.RestartPolicyConditionNone
490		case swarmapi.RestartOnFailure:
491			rp.Condition = types.RestartPolicyConditionOnFailure
492		case swarmapi.RestartOnAny:
493			rp.Condition = types.RestartPolicyConditionAny
494		default:
495			rp.Condition = types.RestartPolicyConditionAny
496		}
497
498		if p.Delay != nil {
499			delay, _ := gogotypes.DurationFromProto(p.Delay)
500			rp.Delay = &delay
501		}
502		if p.Window != nil {
503			window, _ := gogotypes.DurationFromProto(p.Window)
504			rp.Window = &window
505		}
506
507		rp.MaxAttempts = &p.MaxAttempts
508	}
509	return rp
510}
511
512func restartPolicyToGRPC(p *types.RestartPolicy) (*swarmapi.RestartPolicy, error) {
513	var rp *swarmapi.RestartPolicy
514	if p != nil {
515		rp = &swarmapi.RestartPolicy{}
516
517		switch p.Condition {
518		case types.RestartPolicyConditionNone:
519			rp.Condition = swarmapi.RestartOnNone
520		case types.RestartPolicyConditionOnFailure:
521			rp.Condition = swarmapi.RestartOnFailure
522		case types.RestartPolicyConditionAny:
523			rp.Condition = swarmapi.RestartOnAny
524		default:
525			if string(p.Condition) != "" {
526				return nil, fmt.Errorf("invalid RestartCondition: %q", p.Condition)
527			}
528			rp.Condition = swarmapi.RestartOnAny
529		}
530
531		if p.Delay != nil {
532			rp.Delay = gogotypes.DurationProto(*p.Delay)
533		}
534		if p.Window != nil {
535			rp.Window = gogotypes.DurationProto(*p.Window)
536		}
537		if p.MaxAttempts != nil {
538			rp.MaxAttempts = *p.MaxAttempts
539
540		}
541	}
542	return rp, nil
543}
544
545func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
546	if p == nil {
547		return nil
548	}
549	r := &types.Placement{
550		Constraints: p.Constraints,
551		MaxReplicas: p.MaxReplicas,
552	}
553
554	for _, pref := range p.Preferences {
555		if spread := pref.GetSpread(); spread != nil {
556			r.Preferences = append(r.Preferences, types.PlacementPreference{
557				Spread: &types.SpreadOver{
558					SpreadDescriptor: spread.SpreadDescriptor,
559				},
560			})
561		}
562	}
563
564	for _, plat := range p.Platforms {
565		r.Platforms = append(r.Platforms, types.Platform{
566			Architecture: plat.Architecture,
567			OS:           plat.OS,
568		})
569	}
570
571	return r
572}
573
574func driverFromGRPC(p *swarmapi.Driver) *types.Driver {
575	if p == nil {
576		return nil
577	}
578
579	return &types.Driver{
580		Name:    p.Name,
581		Options: p.Options,
582	}
583}
584
585func driverToGRPC(p *types.Driver) *swarmapi.Driver {
586	if p == nil {
587		return nil
588	}
589
590	return &swarmapi.Driver{
591		Name:    p.Name,
592		Options: p.Options,
593	}
594}
595
596func updateConfigFromGRPC(updateConfig *swarmapi.UpdateConfig) *types.UpdateConfig {
597	if updateConfig == nil {
598		return nil
599	}
600
601	converted := &types.UpdateConfig{
602		Parallelism:     updateConfig.Parallelism,
603		MaxFailureRatio: updateConfig.MaxFailureRatio,
604	}
605
606	converted.Delay = updateConfig.Delay
607	if updateConfig.Monitor != nil {
608		converted.Monitor, _ = gogotypes.DurationFromProto(updateConfig.Monitor)
609	}
610
611	switch updateConfig.FailureAction {
612	case swarmapi.UpdateConfig_PAUSE:
613		converted.FailureAction = types.UpdateFailureActionPause
614	case swarmapi.UpdateConfig_CONTINUE:
615		converted.FailureAction = types.UpdateFailureActionContinue
616	case swarmapi.UpdateConfig_ROLLBACK:
617		converted.FailureAction = types.UpdateFailureActionRollback
618	}
619
620	switch updateConfig.Order {
621	case swarmapi.UpdateConfig_STOP_FIRST:
622		converted.Order = types.UpdateOrderStopFirst
623	case swarmapi.UpdateConfig_START_FIRST:
624		converted.Order = types.UpdateOrderStartFirst
625	}
626
627	return converted
628}
629
630func updateConfigToGRPC(updateConfig *types.UpdateConfig) (*swarmapi.UpdateConfig, error) {
631	if updateConfig == nil {
632		return nil, nil
633	}
634
635	converted := &swarmapi.UpdateConfig{
636		Parallelism:     updateConfig.Parallelism,
637		Delay:           updateConfig.Delay,
638		MaxFailureRatio: updateConfig.MaxFailureRatio,
639	}
640
641	switch updateConfig.FailureAction {
642	case types.UpdateFailureActionPause, "":
643		converted.FailureAction = swarmapi.UpdateConfig_PAUSE
644	case types.UpdateFailureActionContinue:
645		converted.FailureAction = swarmapi.UpdateConfig_CONTINUE
646	case types.UpdateFailureActionRollback:
647		converted.FailureAction = swarmapi.UpdateConfig_ROLLBACK
648	default:
649		return nil, fmt.Errorf("unrecognized update failure action %s", updateConfig.FailureAction)
650	}
651	if updateConfig.Monitor != 0 {
652		converted.Monitor = gogotypes.DurationProto(updateConfig.Monitor)
653	}
654
655	switch updateConfig.Order {
656	case types.UpdateOrderStopFirst, "":
657		converted.Order = swarmapi.UpdateConfig_STOP_FIRST
658	case types.UpdateOrderStartFirst:
659		converted.Order = swarmapi.UpdateConfig_START_FIRST
660	default:
661		return nil, fmt.Errorf("unrecognized update order %s", updateConfig.Order)
662	}
663
664	return converted, nil
665}
666
667func networkAttachmentSpecFromGRPC(attachment swarmapi.NetworkAttachmentSpec) *types.NetworkAttachmentSpec {
668	return &types.NetworkAttachmentSpec{
669		ContainerID: attachment.ContainerID,
670	}
671}
672
673func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
674	taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(taskSpec.Networks))
675	for _, n := range taskSpec.Networks {
676		netConfig := types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverOpts: n.DriverAttachmentOpts}
677		taskNetworks = append(taskNetworks, netConfig)
678	}
679
680	t := types.TaskSpec{
681		Resources:     resourcesFromGRPC(&taskSpec),
682		RestartPolicy: restartPolicyFromGRPC(taskSpec.Restart),
683		Placement:     placementFromGRPC(taskSpec.Placement),
684		LogDriver:     driverFromGRPC(taskSpec.LogDriver),
685		Networks:      taskNetworks,
686		ForceUpdate:   taskSpec.ForceUpdate,
687	}
688
689	switch taskSpec.GetRuntime().(type) {
690	case *swarmapi.TaskSpec_Container, nil:
691		c := taskSpec.GetContainer()
692		if c != nil {
693			t.ContainerSpec = containerSpecFromGRPC(c)
694		}
695	case *swarmapi.TaskSpec_Generic:
696		g := taskSpec.GetGeneric()
697		if g != nil {
698			switch g.Kind {
699			case string(types.RuntimePlugin):
700				var p runtime.PluginSpec
701				if err := proto.Unmarshal(g.Payload.Value, &p); err != nil {
702					return t, errors.Wrap(err, "error unmarshalling plugin spec")
703				}
704				t.PluginSpec = &p
705			}
706		}
707	case *swarmapi.TaskSpec_Attachment:
708		a := taskSpec.GetAttachment()
709		if a != nil {
710			t.NetworkAttachmentSpec = networkAttachmentSpecFromGRPC(*a)
711		}
712		t.Runtime = types.RuntimeNetworkAttachment
713	}
714
715	return t, nil
716}
717