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 }