github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/cluster/convert/service.go (about)

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