github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/daemon/cluster/convert/service.go (about)

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