1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/daemon/cluster/convert/service.go
Sebastiaan van Stijn 687bdc7c71
API: swarm: move PidsLimit to TaskTemplate.Resources
The initial implementation followed the Swarm API, where
PidsLimit is located in ContainerSpec. This is not the
desired place for this property, so moving the field to
TaskTemplate.Resources in our API.

A similar change should be made in the SwarmKit API (likely
keeping the old field for backward compatibility, because
it was merged some releases back)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-05 12:50:38 +02:00

716 lines
21 KiB
Go

package convert // import "github.com/docker/docker/daemon/cluster/convert"
import (
"fmt"
"strings"
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
"github.com/docker/docker/pkg/namesgenerator"
swarmapi "github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/genericresource"
"github.com/gogo/protobuf/proto"
gogotypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
)
var (
// ErrUnsupportedRuntime returns an error if the runtime is not supported by the daemon
ErrUnsupportedRuntime = errors.New("unsupported runtime")
// ErrMismatchedRuntime returns an error if the runtime does not match the provided spec
ErrMismatchedRuntime = errors.New("mismatched Runtime and *Spec fields")
)
// ServiceFromGRPC converts a grpc Service to a Service.
func ServiceFromGRPC(s swarmapi.Service) (types.Service, error) {
curSpec, err := serviceSpecFromGRPC(&s.Spec)
if err != nil {
return types.Service{}, err
}
prevSpec, err := serviceSpecFromGRPC(s.PreviousSpec)
if err != nil {
return types.Service{}, err
}
service := types.Service{
ID: s.ID,
Spec: *curSpec,
PreviousSpec: prevSpec,
Endpoint: endpointFromGRPC(s.Endpoint),
}
// Meta
service.Version.Index = s.Meta.Version.Index
service.CreatedAt, _ = gogotypes.TimestampFromProto(s.Meta.CreatedAt)
service.UpdatedAt, _ = gogotypes.TimestampFromProto(s.Meta.UpdatedAt)
if s.JobStatus != nil {
service.JobStatus = &types.JobStatus{
JobIteration: types.Version{
Index: s.JobStatus.JobIteration.Index,
},
}
service.JobStatus.LastExecution, _ = gogotypes.TimestampFromProto(s.JobStatus.LastExecution)
}
// UpdateStatus
if s.UpdateStatus != nil {
service.UpdateStatus = &types.UpdateStatus{}
switch s.UpdateStatus.State {
case swarmapi.UpdateStatus_UPDATING:
service.UpdateStatus.State = types.UpdateStateUpdating
case swarmapi.UpdateStatus_PAUSED:
service.UpdateStatus.State = types.UpdateStatePaused
case swarmapi.UpdateStatus_COMPLETED:
service.UpdateStatus.State = types.UpdateStateCompleted
case swarmapi.UpdateStatus_ROLLBACK_STARTED:
service.UpdateStatus.State = types.UpdateStateRollbackStarted
case swarmapi.UpdateStatus_ROLLBACK_PAUSED:
service.UpdateStatus.State = types.UpdateStateRollbackPaused
case swarmapi.UpdateStatus_ROLLBACK_COMPLETED:
service.UpdateStatus.State = types.UpdateStateRollbackCompleted
}
startedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.StartedAt)
if !startedAt.IsZero() && startedAt.Unix() != 0 {
service.UpdateStatus.StartedAt = &startedAt
}
completedAt, _ := gogotypes.TimestampFromProto(s.UpdateStatus.CompletedAt)
if !completedAt.IsZero() && completedAt.Unix() != 0 {
service.UpdateStatus.CompletedAt = &completedAt
}
service.UpdateStatus.Message = s.UpdateStatus.Message
}
return service, nil
}
func serviceSpecFromGRPC(spec *swarmapi.ServiceSpec) (*types.ServiceSpec, error) {
if spec == nil {
return nil, nil
}
serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
for _, n := range spec.Networks {
netConfig := types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverOpts: n.DriverAttachmentOpts}
serviceNetworks = append(serviceNetworks, netConfig)
}
taskTemplate, err := taskSpecFromGRPC(spec.Task)
if err != nil {
return nil, err
}
switch t := spec.Task.GetRuntime().(type) {
case *swarmapi.TaskSpec_Container:
containerConfig := t.Container
taskTemplate.ContainerSpec = containerSpecFromGRPC(containerConfig)
taskTemplate.Runtime = types.RuntimeContainer
case *swarmapi.TaskSpec_Generic:
switch t.Generic.Kind {
case string(types.RuntimePlugin):
taskTemplate.Runtime = types.RuntimePlugin
default:
return nil, fmt.Errorf("unknown task runtime type: %s", t.Generic.Payload.TypeUrl)
}
default:
return nil, fmt.Errorf("error creating service; unsupported runtime %T", t)
}
convertedSpec := &types.ServiceSpec{
Annotations: annotationsFromGRPC(spec.Annotations),
TaskTemplate: taskTemplate,
Networks: serviceNetworks,
EndpointSpec: endpointSpecFromGRPC(spec.Endpoint),
}
// UpdateConfig
convertedSpec.UpdateConfig = updateConfigFromGRPC(spec.Update)
convertedSpec.RollbackConfig = updateConfigFromGRPC(spec.Rollback)
// Mode
switch t := spec.GetMode().(type) {
case *swarmapi.ServiceSpec_Global:
convertedSpec.Mode.Global = &types.GlobalService{}
case *swarmapi.ServiceSpec_Replicated:
convertedSpec.Mode.Replicated = &types.ReplicatedService{
Replicas: &t.Replicated.Replicas,
}
case *swarmapi.ServiceSpec_ReplicatedJob:
convertedSpec.Mode.ReplicatedJob = &types.ReplicatedJob{
MaxConcurrent: &t.ReplicatedJob.MaxConcurrent,
TotalCompletions: &t.ReplicatedJob.TotalCompletions,
}
case *swarmapi.ServiceSpec_GlobalJob:
convertedSpec.Mode.GlobalJob = &types.GlobalJob{}
}
return convertedSpec, nil
}
// ServiceSpecToGRPC converts a ServiceSpec to a grpc ServiceSpec.
func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
name := s.Name
if name == "" {
name = namesgenerator.GetRandomName(0)
}
serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
for _, n := range s.Networks {
netConfig := &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverAttachmentOpts: n.DriverOpts}
serviceNetworks = append(serviceNetworks, netConfig)
}
taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
for _, n := range s.TaskTemplate.Networks {
netConfig := &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverAttachmentOpts: n.DriverOpts}
taskNetworks = append(taskNetworks, netConfig)
}
spec := swarmapi.ServiceSpec{
Annotations: swarmapi.Annotations{
Name: name,
Labels: s.Labels,
},
Task: swarmapi.TaskSpec{
Resources: resourcesToGRPC(s.TaskTemplate.Resources),
LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
Networks: taskNetworks,
ForceUpdate: s.TaskTemplate.ForceUpdate,
},
Networks: serviceNetworks,
}
switch s.TaskTemplate.Runtime {
case types.RuntimeContainer, "": // if empty runtime default to container
if s.TaskTemplate.ContainerSpec != nil {
containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
if err != nil {
return swarmapi.ServiceSpec{}, err
}
if s.TaskTemplate.Resources != nil && s.TaskTemplate.Resources.Limits != nil {
// TODO remove this (or keep for backward compat) once SwarmKit API moved PidsLimit into Resources
containerSpec.PidsLimit = s.TaskTemplate.Resources.Limits.Pids
}
spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
} else {
// If the ContainerSpec is nil, we can't set the task runtime
return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
}
case types.RuntimePlugin:
if s.TaskTemplate.PluginSpec != nil {
if s.Mode.Replicated != nil {
return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
}
s.Mode.Global = &types.GlobalService{} // must always be global
pluginSpec, err := proto.Marshal(s.TaskTemplate.PluginSpec)
if err != nil {
return swarmapi.ServiceSpec{}, err
}
spec.Task.Runtime = &swarmapi.TaskSpec_Generic{
Generic: &swarmapi.GenericRuntimeSpec{
Kind: string(types.RuntimePlugin),
Payload: &gogotypes.Any{
TypeUrl: string(types.RuntimeURLPlugin),
Value: pluginSpec,
},
},
}
} else {
return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
}
case types.RuntimeNetworkAttachment:
// NOTE(dperny) I'm leaving this case here for completeness. The actual
// code is left out deliberately, as we should refuse to parse a
// Network Attachment runtime; it will cause weird behavior all over
// the system if we do. Instead, fallthrough and return
// ErrUnsupportedRuntime if we get one.
fallthrough
default:
return swarmapi.ServiceSpec{}, ErrUnsupportedRuntime
}
restartPolicy, err := restartPolicyToGRPC(s.TaskTemplate.RestartPolicy)
if err != nil {
return swarmapi.ServiceSpec{}, err
}
spec.Task.Restart = restartPolicy
if s.TaskTemplate.Placement != nil {
var preferences []*swarmapi.PlacementPreference
for _, pref := range s.TaskTemplate.Placement.Preferences {
if pref.Spread != nil {
preferences = append(preferences, &swarmapi.PlacementPreference{
Preference: &swarmapi.PlacementPreference_Spread{
Spread: &swarmapi.SpreadOver{
SpreadDescriptor: pref.Spread.SpreadDescriptor,
},
},
})
}
}
var platforms []*swarmapi.Platform
for _, plat := range s.TaskTemplate.Placement.Platforms {
platforms = append(platforms, &swarmapi.Platform{
Architecture: plat.Architecture,
OS: plat.OS,
})
}
spec.Task.Placement = &swarmapi.Placement{
Constraints: s.TaskTemplate.Placement.Constraints,
Preferences: preferences,
MaxReplicas: s.TaskTemplate.Placement.MaxReplicas,
Platforms: platforms,
}
}
spec.Update, err = updateConfigToGRPC(s.UpdateConfig)
if err != nil {
return swarmapi.ServiceSpec{}, err
}
spec.Rollback, err = updateConfigToGRPC(s.RollbackConfig)
if err != nil {
return swarmapi.ServiceSpec{}, err
}
if s.EndpointSpec != nil {
if s.EndpointSpec.Mode != "" &&
s.EndpointSpec.Mode != types.ResolutionModeVIP &&
s.EndpointSpec.Mode != types.ResolutionModeDNSRR {
return swarmapi.ServiceSpec{}, fmt.Errorf("invalid resolution mode: %q", s.EndpointSpec.Mode)
}
spec.Endpoint = &swarmapi.EndpointSpec{}
spec.Endpoint.Mode = swarmapi.EndpointSpec_ResolutionMode(swarmapi.EndpointSpec_ResolutionMode_value[strings.ToUpper(string(s.EndpointSpec.Mode))])
for _, portConfig := range s.EndpointSpec.Ports {
spec.Endpoint.Ports = append(spec.Endpoint.Ports, &swarmapi.PortConfig{
Name: portConfig.Name,
Protocol: swarmapi.PortConfig_Protocol(swarmapi.PortConfig_Protocol_value[strings.ToUpper(string(portConfig.Protocol))]),
PublishMode: swarmapi.PortConfig_PublishMode(swarmapi.PortConfig_PublishMode_value[strings.ToUpper(string(portConfig.PublishMode))]),
TargetPort: portConfig.TargetPort,
PublishedPort: portConfig.PublishedPort,
})
}
}
// Mode
numModes := 0
if s.Mode.Global != nil {
numModes++
}
if s.Mode.Replicated != nil {
numModes++
}
if s.Mode.ReplicatedJob != nil {
numModes++
}
if s.Mode.GlobalJob != nil {
numModes++
}
if numModes > 1 {
return swarmapi.ServiceSpec{}, fmt.Errorf("must specify only one service mode")
}
if s.Mode.Global != nil {
spec.Mode = &swarmapi.ServiceSpec_Global{
Global: &swarmapi.GlobalService{},
}
} else if s.Mode.GlobalJob != nil {
spec.Mode = &swarmapi.ServiceSpec_GlobalJob{
GlobalJob: &swarmapi.GlobalJob{},
}
} else if s.Mode.ReplicatedJob != nil {
// if the service is a replicated job, we have two different kinds of
// values that might need to be defaulted.
r := &swarmapi.ReplicatedJob{}
if s.Mode.ReplicatedJob.MaxConcurrent != nil {
r.MaxConcurrent = *s.Mode.ReplicatedJob.MaxConcurrent
} else {
r.MaxConcurrent = 1
}
if s.Mode.ReplicatedJob.TotalCompletions != nil {
r.TotalCompletions = *s.Mode.ReplicatedJob.TotalCompletions
} else {
r.TotalCompletions = r.MaxConcurrent
}
spec.Mode = &swarmapi.ServiceSpec_ReplicatedJob{
ReplicatedJob: r,
}
} else if s.Mode.Replicated != nil && s.Mode.Replicated.Replicas != nil {
spec.Mode = &swarmapi.ServiceSpec_Replicated{
Replicated: &swarmapi.ReplicatedService{Replicas: *s.Mode.Replicated.Replicas},
}
} else {
spec.Mode = &swarmapi.ServiceSpec_Replicated{
Replicated: &swarmapi.ReplicatedService{Replicas: 1},
}
}
return spec, nil
}
func annotationsFromGRPC(ann swarmapi.Annotations) types.Annotations {
a := types.Annotations{
Name: ann.Name,
Labels: ann.Labels,
}
if a.Labels == nil {
a.Labels = make(map[string]string)
}
return a
}
// GenericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
func GenericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []types.GenericResource {
var generic []types.GenericResource
for _, res := range genericRes {
var current types.GenericResource
switch r := res.Resource.(type) {
case *swarmapi.GenericResource_DiscreteResourceSpec:
current.DiscreteResourceSpec = &types.DiscreteGenericResource{
Kind: r.DiscreteResourceSpec.Kind,
Value: r.DiscreteResourceSpec.Value,
}
case *swarmapi.GenericResource_NamedResourceSpec:
current.NamedResourceSpec = &types.NamedGenericResource{
Kind: r.NamedResourceSpec.Kind,
Value: r.NamedResourceSpec.Value,
}
}
generic = append(generic, current)
}
return generic
}
// resourcesFromGRPC creates a ResourceRequirements from the GRPC TaskSpec.
// We currently require the whole TaskSpec to be passed, because PidsLimit
// is returned as part of the container spec, instead of Resources
// TODO move PidsLimit to Resources in the Swarm API
func resourcesFromGRPC(ts *swarmapi.TaskSpec) *types.ResourceRequirements {
var resources *types.ResourceRequirements
if cs := ts.GetContainer(); cs != nil && cs.PidsLimit != 0 {
resources = &types.ResourceRequirements{
Limits: &types.Limit{
Pids: cs.PidsLimit,
},
}
}
if ts.Resources != nil {
if resources == nil {
resources = &types.ResourceRequirements{}
}
res := ts.Resources
if res.Limits != nil {
if resources.Limits == nil {
resources.Limits = &types.Limit{}
}
resources.Limits.NanoCPUs = res.Limits.NanoCPUs
resources.Limits.MemoryBytes = res.Limits.MemoryBytes
}
if res.Reservations != nil {
resources.Reservations = &types.Resources{
NanoCPUs: res.Reservations.NanoCPUs,
MemoryBytes: res.Reservations.MemoryBytes,
GenericResources: GenericResourcesFromGRPC(res.Reservations.Generic),
}
}
}
return resources
}
// GenericResourcesToGRPC converts a GenericResource to a GRPC GenericResource
func GenericResourcesToGRPC(genericRes []types.GenericResource) []*swarmapi.GenericResource {
var generic []*swarmapi.GenericResource
for _, res := range genericRes {
var r *swarmapi.GenericResource
if res.DiscreteResourceSpec != nil {
r = genericresource.NewDiscrete(res.DiscreteResourceSpec.Kind, res.DiscreteResourceSpec.Value)
} else if res.NamedResourceSpec != nil {
r = genericresource.NewString(res.NamedResourceSpec.Kind, res.NamedResourceSpec.Value)
}
generic = append(generic, r)
}
return generic
}
func resourcesToGRPC(res *types.ResourceRequirements) *swarmapi.ResourceRequirements {
var reqs *swarmapi.ResourceRequirements
if res != nil {
reqs = &swarmapi.ResourceRequirements{}
if res.Limits != nil {
// TODO add PidsLimit once Swarm API has been updated to move it into Limits
reqs.Limits = &swarmapi.Resources{
NanoCPUs: res.Limits.NanoCPUs,
MemoryBytes: res.Limits.MemoryBytes,
}
}
if res.Reservations != nil {
reqs.Reservations = &swarmapi.Resources{
NanoCPUs: res.Reservations.NanoCPUs,
MemoryBytes: res.Reservations.MemoryBytes,
Generic: GenericResourcesToGRPC(res.Reservations.GenericResources),
}
}
}
return reqs
}
func restartPolicyFromGRPC(p *swarmapi.RestartPolicy) *types.RestartPolicy {
var rp *types.RestartPolicy
if p != nil {
rp = &types.RestartPolicy{}
switch p.Condition {
case swarmapi.RestartOnNone:
rp.Condition = types.RestartPolicyConditionNone
case swarmapi.RestartOnFailure:
rp.Condition = types.RestartPolicyConditionOnFailure
case swarmapi.RestartOnAny:
rp.Condition = types.RestartPolicyConditionAny
default:
rp.Condition = types.RestartPolicyConditionAny
}
if p.Delay != nil {
delay, _ := gogotypes.DurationFromProto(p.Delay)
rp.Delay = &delay
}
if p.Window != nil {
window, _ := gogotypes.DurationFromProto(p.Window)
rp.Window = &window
}
rp.MaxAttempts = &p.MaxAttempts
}
return rp
}
func restartPolicyToGRPC(p *types.RestartPolicy) (*swarmapi.RestartPolicy, error) {
var rp *swarmapi.RestartPolicy
if p != nil {
rp = &swarmapi.RestartPolicy{}
switch p.Condition {
case types.RestartPolicyConditionNone:
rp.Condition = swarmapi.RestartOnNone
case types.RestartPolicyConditionOnFailure:
rp.Condition = swarmapi.RestartOnFailure
case types.RestartPolicyConditionAny:
rp.Condition = swarmapi.RestartOnAny
default:
if string(p.Condition) != "" {
return nil, fmt.Errorf("invalid RestartCondition: %q", p.Condition)
}
rp.Condition = swarmapi.RestartOnAny
}
if p.Delay != nil {
rp.Delay = gogotypes.DurationProto(*p.Delay)
}
if p.Window != nil {
rp.Window = gogotypes.DurationProto(*p.Window)
}
if p.MaxAttempts != nil {
rp.MaxAttempts = *p.MaxAttempts
}
}
return rp, nil
}
func placementFromGRPC(p *swarmapi.Placement) *types.Placement {
if p == nil {
return nil
}
r := &types.Placement{
Constraints: p.Constraints,
MaxReplicas: p.MaxReplicas,
}
for _, pref := range p.Preferences {
if spread := pref.GetSpread(); spread != nil {
r.Preferences = append(r.Preferences, types.PlacementPreference{
Spread: &types.SpreadOver{
SpreadDescriptor: spread.SpreadDescriptor,
},
})
}
}
for _, plat := range p.Platforms {
r.Platforms = append(r.Platforms, types.Platform{
Architecture: plat.Architecture,
OS: plat.OS,
})
}
return r
}
func driverFromGRPC(p *swarmapi.Driver) *types.Driver {
if p == nil {
return nil
}
return &types.Driver{
Name: p.Name,
Options: p.Options,
}
}
func driverToGRPC(p *types.Driver) *swarmapi.Driver {
if p == nil {
return nil
}
return &swarmapi.Driver{
Name: p.Name,
Options: p.Options,
}
}
func updateConfigFromGRPC(updateConfig *swarmapi.UpdateConfig) *types.UpdateConfig {
if updateConfig == nil {
return nil
}
converted := &types.UpdateConfig{
Parallelism: updateConfig.Parallelism,
MaxFailureRatio: updateConfig.MaxFailureRatio,
}
converted.Delay = updateConfig.Delay
if updateConfig.Monitor != nil {
converted.Monitor, _ = gogotypes.DurationFromProto(updateConfig.Monitor)
}
switch updateConfig.FailureAction {
case swarmapi.UpdateConfig_PAUSE:
converted.FailureAction = types.UpdateFailureActionPause
case swarmapi.UpdateConfig_CONTINUE:
converted.FailureAction = types.UpdateFailureActionContinue
case swarmapi.UpdateConfig_ROLLBACK:
converted.FailureAction = types.UpdateFailureActionRollback
}
switch updateConfig.Order {
case swarmapi.UpdateConfig_STOP_FIRST:
converted.Order = types.UpdateOrderStopFirst
case swarmapi.UpdateConfig_START_FIRST:
converted.Order = types.UpdateOrderStartFirst
}
return converted
}
func updateConfigToGRPC(updateConfig *types.UpdateConfig) (*swarmapi.UpdateConfig, error) {
if updateConfig == nil {
return nil, nil
}
converted := &swarmapi.UpdateConfig{
Parallelism: updateConfig.Parallelism,
Delay: updateConfig.Delay,
MaxFailureRatio: updateConfig.MaxFailureRatio,
}
switch updateConfig.FailureAction {
case types.UpdateFailureActionPause, "":
converted.FailureAction = swarmapi.UpdateConfig_PAUSE
case types.UpdateFailureActionContinue:
converted.FailureAction = swarmapi.UpdateConfig_CONTINUE
case types.UpdateFailureActionRollback:
converted.FailureAction = swarmapi.UpdateConfig_ROLLBACK
default:
return nil, fmt.Errorf("unrecognized update failure action %s", updateConfig.FailureAction)
}
if updateConfig.Monitor != 0 {
converted.Monitor = gogotypes.DurationProto(updateConfig.Monitor)
}
switch updateConfig.Order {
case types.UpdateOrderStopFirst, "":
converted.Order = swarmapi.UpdateConfig_STOP_FIRST
case types.UpdateOrderStartFirst:
converted.Order = swarmapi.UpdateConfig_START_FIRST
default:
return nil, fmt.Errorf("unrecognized update order %s", updateConfig.Order)
}
return converted, nil
}
func networkAttachmentSpecFromGRPC(attachment swarmapi.NetworkAttachmentSpec) *types.NetworkAttachmentSpec {
return &types.NetworkAttachmentSpec{
ContainerID: attachment.ContainerID,
}
}
func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(taskSpec.Networks))
for _, n := range taskSpec.Networks {
netConfig := types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases, DriverOpts: n.DriverAttachmentOpts}
taskNetworks = append(taskNetworks, netConfig)
}
t := types.TaskSpec{
Resources: resourcesFromGRPC(&taskSpec),
RestartPolicy: restartPolicyFromGRPC(taskSpec.Restart),
Placement: placementFromGRPC(taskSpec.Placement),
LogDriver: driverFromGRPC(taskSpec.LogDriver),
Networks: taskNetworks,
ForceUpdate: taskSpec.ForceUpdate,
}
switch taskSpec.GetRuntime().(type) {
case *swarmapi.TaskSpec_Container, nil:
c := taskSpec.GetContainer()
if c != nil {
t.ContainerSpec = containerSpecFromGRPC(c)
}
case *swarmapi.TaskSpec_Generic:
g := taskSpec.GetGeneric()
if g != nil {
switch g.Kind {
case string(types.RuntimePlugin):
var p runtime.PluginSpec
if err := proto.Unmarshal(g.Payload.Value, &p); err != nil {
return t, errors.Wrap(err, "error unmarshalling plugin spec")
}
t.PluginSpec = &p
}
}
case *swarmapi.TaskSpec_Attachment:
a := taskSpec.GetAttachment()
if a != nil {
t.NetworkAttachmentSpec = networkAttachmentSpecFromGRPC(*a)
}
t.Runtime = types.RuntimeNetworkAttachment
}
return t, nil
}