github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/daemon/cluster/convert/service.go (about)

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