github.com/moby/docker@v26.1.3+incompatible/daemon/cluster/convert/container.go (about) 1 package convert // import "github.com/docker/docker/daemon/cluster/convert" 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/containerd/log" 9 "github.com/docker/docker/api/types/container" 10 mounttypes "github.com/docker/docker/api/types/mount" 11 types "github.com/docker/docker/api/types/swarm" 12 "github.com/docker/go-units" 13 gogotypes "github.com/gogo/protobuf/types" 14 swarmapi "github.com/moby/swarmkit/v2/api" 15 "github.com/pkg/errors" 16 ) 17 18 func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec { 19 if c == nil { 20 return nil 21 } 22 containerSpec := &types.ContainerSpec{ 23 Image: c.Image, 24 Labels: c.Labels, 25 Command: c.Command, 26 Args: c.Args, 27 Hostname: c.Hostname, 28 Env: c.Env, 29 Dir: c.Dir, 30 User: c.User, 31 Groups: c.Groups, 32 StopSignal: c.StopSignal, 33 TTY: c.TTY, 34 OpenStdin: c.OpenStdin, 35 ReadOnly: c.ReadOnly, 36 Hosts: c.Hosts, 37 Secrets: secretReferencesFromGRPC(c.Secrets), 38 Configs: configReferencesFromGRPC(c.Configs), 39 Isolation: IsolationFromGRPC(c.Isolation), 40 Init: initFromGRPC(c.Init), 41 Sysctls: c.Sysctls, 42 CapabilityAdd: c.CapabilityAdd, 43 CapabilityDrop: c.CapabilityDrop, 44 Ulimits: ulimitsFromGRPC(c.Ulimits), 45 } 46 47 if c.DNSConfig != nil { 48 containerSpec.DNSConfig = &types.DNSConfig{ 49 Nameservers: c.DNSConfig.Nameservers, 50 Search: c.DNSConfig.Search, 51 Options: c.DNSConfig.Options, 52 } 53 } 54 55 // Privileges 56 if c.Privileges != nil { 57 containerSpec.Privileges = &types.Privileges{} 58 59 if c.Privileges.CredentialSpec != nil { 60 containerSpec.Privileges.CredentialSpec = credentialSpecFromGRPC(c.Privileges.CredentialSpec) 61 } 62 63 if c.Privileges.SELinuxContext != nil { 64 containerSpec.Privileges.SELinuxContext = &types.SELinuxContext{ 65 Disable: c.Privileges.SELinuxContext.Disable, 66 User: c.Privileges.SELinuxContext.User, 67 Type: c.Privileges.SELinuxContext.Type, 68 Role: c.Privileges.SELinuxContext.Role, 69 Level: c.Privileges.SELinuxContext.Level, 70 } 71 } 72 73 if c.Privileges.Seccomp != nil { 74 containerSpec.Privileges.Seccomp = &types.SeccompOpts{ 75 Profile: c.Privileges.Seccomp.Profile, 76 } 77 78 switch c.Privileges.Seccomp.Mode { 79 case swarmapi.Privileges_SeccompOpts_DEFAULT: 80 containerSpec.Privileges.Seccomp.Mode = types.SeccompModeDefault 81 case swarmapi.Privileges_SeccompOpts_UNCONFINED: 82 containerSpec.Privileges.Seccomp.Mode = types.SeccompModeUnconfined 83 case swarmapi.Privileges_SeccompOpts_CUSTOM: 84 containerSpec.Privileges.Seccomp.Mode = types.SeccompModeCustom 85 } 86 } 87 88 if c.Privileges.Apparmor != nil { 89 containerSpec.Privileges.AppArmor = &types.AppArmorOpts{} 90 91 switch c.Privileges.Apparmor.Mode { 92 case swarmapi.Privileges_AppArmorOpts_DEFAULT: 93 containerSpec.Privileges.AppArmor.Mode = types.AppArmorModeDefault 94 case swarmapi.Privileges_AppArmorOpts_DISABLED: 95 containerSpec.Privileges.AppArmor.Mode = types.AppArmorModeDisabled 96 } 97 } 98 99 containerSpec.Privileges.NoNewPrivileges = c.Privileges.NoNewPrivileges 100 } 101 102 // Mounts 103 for _, m := range c.Mounts { 104 mount := mounttypes.Mount{ 105 Target: m.Target, 106 Source: m.Source, 107 Type: mounttypes.Type(strings.ToLower(swarmapi.Mount_MountType_name[int32(m.Type)])), 108 ReadOnly: m.ReadOnly, 109 } 110 111 if m.BindOptions != nil { 112 mount.BindOptions = &mounttypes.BindOptions{ 113 Propagation: mounttypes.Propagation(strings.ToLower(swarmapi.Mount_BindOptions_MountPropagation_name[int32(m.BindOptions.Propagation)])), 114 NonRecursive: m.BindOptions.NonRecursive, 115 CreateMountpoint: m.BindOptions.CreateMountpoint, 116 ReadOnlyNonRecursive: m.BindOptions.ReadOnlyNonRecursive, 117 ReadOnlyForceRecursive: m.BindOptions.ReadOnlyForceRecursive, 118 } 119 } 120 121 if m.VolumeOptions != nil { 122 mount.VolumeOptions = &mounttypes.VolumeOptions{ 123 NoCopy: m.VolumeOptions.NoCopy, 124 Labels: m.VolumeOptions.Labels, 125 Subpath: m.VolumeOptions.Subpath, 126 } 127 if m.VolumeOptions.DriverConfig != nil { 128 mount.VolumeOptions.DriverConfig = &mounttypes.Driver{ 129 Name: m.VolumeOptions.DriverConfig.Name, 130 Options: m.VolumeOptions.DriverConfig.Options, 131 } 132 } 133 } 134 135 if m.TmpfsOptions != nil { 136 mount.TmpfsOptions = &mounttypes.TmpfsOptions{ 137 SizeBytes: m.TmpfsOptions.SizeBytes, 138 Mode: m.TmpfsOptions.Mode, 139 } 140 } 141 containerSpec.Mounts = append(containerSpec.Mounts, mount) 142 } 143 144 if c.StopGracePeriod != nil { 145 grace, _ := gogotypes.DurationFromProto(c.StopGracePeriod) 146 containerSpec.StopGracePeriod = &grace 147 } 148 149 if c.Healthcheck != nil { 150 containerSpec.Healthcheck = healthConfigFromGRPC(c.Healthcheck) 151 } 152 153 return containerSpec 154 } 155 156 func initFromGRPC(v *gogotypes.BoolValue) *bool { 157 if v == nil { 158 return nil 159 } 160 value := v.GetValue() 161 return &value 162 } 163 164 func initToGRPC(v *bool) *gogotypes.BoolValue { 165 if v == nil { 166 return nil 167 } 168 return &gogotypes.BoolValue{Value: *v} 169 } 170 171 func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretReference { 172 refs := make([]*swarmapi.SecretReference, 0, len(sr)) 173 for _, s := range sr { 174 ref := &swarmapi.SecretReference{ 175 SecretID: s.SecretID, 176 SecretName: s.SecretName, 177 } 178 if s.File != nil { 179 ref.Target = &swarmapi.SecretReference_File{ 180 File: &swarmapi.FileTarget{ 181 Name: s.File.Name, 182 UID: s.File.UID, 183 GID: s.File.GID, 184 Mode: s.File.Mode, 185 }, 186 } 187 } 188 189 refs = append(refs, ref) 190 } 191 192 return refs 193 } 194 195 func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretReference { 196 refs := make([]*types.SecretReference, 0, len(sr)) 197 for _, s := range sr { 198 target := s.GetFile() 199 if target == nil { 200 // not a file target 201 log.G(context.TODO()).Warnf("secret target not a file: secret=%s", s.SecretID) 202 continue 203 } 204 refs = append(refs, &types.SecretReference{ 205 File: &types.SecretReferenceFileTarget{ 206 Name: target.Name, 207 UID: target.UID, 208 GID: target.GID, 209 Mode: target.Mode, 210 }, 211 SecretID: s.SecretID, 212 SecretName: s.SecretName, 213 }) 214 } 215 216 return refs 217 } 218 219 func configReferencesToGRPC(sr []*types.ConfigReference) ([]*swarmapi.ConfigReference, error) { 220 refs := make([]*swarmapi.ConfigReference, 0, len(sr)) 221 for _, s := range sr { 222 ref := &swarmapi.ConfigReference{ 223 ConfigID: s.ConfigID, 224 ConfigName: s.ConfigName, 225 } 226 switch { 227 case s.Runtime == nil && s.File == nil: 228 return nil, errors.New("either File or Runtime should be set") 229 case s.Runtime != nil && s.File != nil: 230 return nil, errors.New("cannot specify both File and Runtime") 231 case s.Runtime != nil: 232 // Runtime target was added in API v1.40 and takes precedence over 233 // File target. However, File and Runtime targets are mutually exclusive, 234 // so we should never have both. 235 ref.Target = &swarmapi.ConfigReference_Runtime{ 236 Runtime: &swarmapi.RuntimeTarget{}, 237 } 238 case s.File != nil: 239 ref.Target = &swarmapi.ConfigReference_File{ 240 File: &swarmapi.FileTarget{ 241 Name: s.File.Name, 242 UID: s.File.UID, 243 GID: s.File.GID, 244 Mode: s.File.Mode, 245 }, 246 } 247 } 248 249 refs = append(refs, ref) 250 } 251 252 return refs, nil 253 } 254 255 func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference { 256 refs := make([]*types.ConfigReference, 0, len(sr)) 257 for _, s := range sr { 258 r := &types.ConfigReference{ 259 ConfigID: s.ConfigID, 260 ConfigName: s.ConfigName, 261 } 262 if target := s.GetRuntime(); target != nil { 263 r.Runtime = &types.ConfigReferenceRuntimeTarget{} 264 } else if target := s.GetFile(); target != nil { 265 r.File = &types.ConfigReferenceFileTarget{ 266 Name: target.Name, 267 UID: target.UID, 268 GID: target.GID, 269 Mode: target.Mode, 270 } 271 } else { 272 // not a file target 273 log.G(context.TODO()).Warnf("config target not known: config=%s", s.ConfigID) 274 continue 275 } 276 refs = append(refs, r) 277 } 278 279 return refs 280 } 281 282 func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) { 283 containerSpec := &swarmapi.ContainerSpec{ 284 Image: c.Image, 285 Labels: c.Labels, 286 Command: c.Command, 287 Args: c.Args, 288 Hostname: c.Hostname, 289 Env: c.Env, 290 Dir: c.Dir, 291 User: c.User, 292 Groups: c.Groups, 293 StopSignal: c.StopSignal, 294 TTY: c.TTY, 295 OpenStdin: c.OpenStdin, 296 ReadOnly: c.ReadOnly, 297 Hosts: c.Hosts, 298 Secrets: secretReferencesToGRPC(c.Secrets), 299 Isolation: isolationToGRPC(c.Isolation), 300 Init: initToGRPC(c.Init), 301 Sysctls: c.Sysctls, 302 CapabilityAdd: c.CapabilityAdd, 303 CapabilityDrop: c.CapabilityDrop, 304 Ulimits: ulimitsToGRPC(c.Ulimits), 305 } 306 307 if c.DNSConfig != nil { 308 containerSpec.DNSConfig = &swarmapi.ContainerSpec_DNSConfig{ 309 Nameservers: c.DNSConfig.Nameservers, 310 Search: c.DNSConfig.Search, 311 Options: c.DNSConfig.Options, 312 } 313 } 314 315 if c.StopGracePeriod != nil { 316 containerSpec.StopGracePeriod = gogotypes.DurationProto(*c.StopGracePeriod) 317 } 318 319 // Privileges 320 if c.Privileges != nil { 321 containerSpec.Privileges = &swarmapi.Privileges{} 322 323 if c.Privileges.CredentialSpec != nil { 324 cs, err := credentialSpecToGRPC(c.Privileges.CredentialSpec) 325 if err != nil { 326 return nil, errors.Wrap(err, "invalid CredentialSpec") 327 } 328 containerSpec.Privileges.CredentialSpec = cs 329 } 330 331 if c.Privileges.SELinuxContext != nil { 332 containerSpec.Privileges.SELinuxContext = &swarmapi.Privileges_SELinuxContext{ 333 Disable: c.Privileges.SELinuxContext.Disable, 334 User: c.Privileges.SELinuxContext.User, 335 Type: c.Privileges.SELinuxContext.Type, 336 Role: c.Privileges.SELinuxContext.Role, 337 Level: c.Privileges.SELinuxContext.Level, 338 } 339 } 340 341 if c.Privileges.Seccomp != nil { 342 containerSpec.Privileges.Seccomp = &swarmapi.Privileges_SeccompOpts{ 343 Profile: c.Privileges.Seccomp.Profile, 344 } 345 346 switch c.Privileges.Seccomp.Mode { 347 case types.SeccompModeDefault: 348 containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_DEFAULT 349 case types.SeccompModeUnconfined: 350 containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_UNCONFINED 351 case types.SeccompModeCustom: 352 containerSpec.Privileges.Seccomp.Mode = swarmapi.Privileges_SeccompOpts_CUSTOM 353 } 354 } 355 356 if c.Privileges.AppArmor != nil { 357 containerSpec.Privileges.Apparmor = &swarmapi.Privileges_AppArmorOpts{} 358 359 switch c.Privileges.AppArmor.Mode { 360 case types.AppArmorModeDefault: 361 containerSpec.Privileges.Apparmor.Mode = swarmapi.Privileges_AppArmorOpts_DEFAULT 362 case types.AppArmorModeDisabled: 363 containerSpec.Privileges.Apparmor.Mode = swarmapi.Privileges_AppArmorOpts_DISABLED 364 } 365 } 366 367 containerSpec.Privileges.NoNewPrivileges = c.Privileges.NoNewPrivileges 368 } 369 370 if c.Configs != nil { 371 configs, err := configReferencesToGRPC(c.Configs) 372 if err != nil { 373 return nil, errors.Wrap(err, "invalid Config") 374 } 375 containerSpec.Configs = configs 376 } 377 378 // Mounts 379 for _, m := range c.Mounts { 380 mount := swarmapi.Mount{ 381 Target: m.Target, 382 Source: m.Source, 383 ReadOnly: m.ReadOnly, 384 } 385 386 if mountType, ok := swarmapi.Mount_MountType_value[strings.ToUpper(string(m.Type))]; ok { 387 mount.Type = swarmapi.Mount_MountType(mountType) 388 } else if string(m.Type) != "" { 389 return nil, fmt.Errorf("invalid MountType: %q", m.Type) 390 } 391 392 if m.BindOptions != nil { 393 if mountPropagation, ok := swarmapi.Mount_BindOptions_MountPropagation_value[strings.ToUpper(string(m.BindOptions.Propagation))]; ok { 394 mount.BindOptions = &swarmapi.Mount_BindOptions{Propagation: swarmapi.Mount_BindOptions_MountPropagation(mountPropagation)} 395 } else if string(m.BindOptions.Propagation) != "" { 396 return nil, fmt.Errorf("invalid MountPropagation: %q", m.BindOptions.Propagation) 397 } 398 399 if m.BindOptions.NonRecursive { 400 if mount.BindOptions == nil { 401 // the propagation defaults to rprivate 402 mount.BindOptions = &swarmapi.Mount_BindOptions{} 403 } 404 mount.BindOptions.NonRecursive = m.BindOptions.NonRecursive 405 } 406 } 407 408 if m.VolumeOptions != nil { 409 mount.VolumeOptions = &swarmapi.Mount_VolumeOptions{ 410 NoCopy: m.VolumeOptions.NoCopy, 411 Labels: m.VolumeOptions.Labels, 412 Subpath: m.VolumeOptions.Subpath, 413 } 414 if m.VolumeOptions.DriverConfig != nil { 415 mount.VolumeOptions.DriverConfig = &swarmapi.Driver{ 416 Name: m.VolumeOptions.DriverConfig.Name, 417 Options: m.VolumeOptions.DriverConfig.Options, 418 } 419 } 420 } 421 422 if m.TmpfsOptions != nil { 423 mount.TmpfsOptions = &swarmapi.Mount_TmpfsOptions{ 424 SizeBytes: m.TmpfsOptions.SizeBytes, 425 Mode: m.TmpfsOptions.Mode, 426 } 427 } 428 429 containerSpec.Mounts = append(containerSpec.Mounts, mount) 430 } 431 432 if c.Healthcheck != nil { 433 containerSpec.Healthcheck = healthConfigToGRPC(c.Healthcheck) 434 } 435 436 return containerSpec, nil 437 } 438 439 func credentialSpecFromGRPC(c *swarmapi.Privileges_CredentialSpec) *types.CredentialSpec { 440 cs := &types.CredentialSpec{} 441 switch c.Source.(type) { 442 case *swarmapi.Privileges_CredentialSpec_Config: 443 cs.Config = c.GetConfig() 444 case *swarmapi.Privileges_CredentialSpec_File: 445 cs.File = c.GetFile() 446 case *swarmapi.Privileges_CredentialSpec_Registry: 447 cs.Registry = c.GetRegistry() 448 } 449 return cs 450 } 451 452 func credentialSpecToGRPC(c *types.CredentialSpec) (*swarmapi.Privileges_CredentialSpec, error) { 453 var opts []string 454 455 if c.Config != "" { 456 opts = append(opts, `"config"`) 457 } 458 if c.File != "" { 459 opts = append(opts, `"file"`) 460 } 461 if c.Registry != "" { 462 opts = append(opts, `"registry"`) 463 } 464 l := len(opts) 465 switch { 466 case l == 0: 467 return nil, errors.New(`must either provide "file", "registry", or "config" for credential spec`) 468 case l == 2: 469 return nil, fmt.Errorf("cannot specify both %s and %s credential specs", opts[0], opts[1]) 470 case l > 2: 471 return nil, fmt.Errorf("cannot specify both %s, and %s credential specs", strings.Join(opts[:l-1], ", "), opts[l-1]) 472 } 473 474 spec := &swarmapi.Privileges_CredentialSpec{} 475 switch { 476 case c.Config != "": 477 spec.Source = &swarmapi.Privileges_CredentialSpec_Config{ 478 Config: c.Config, 479 } 480 case c.File != "": 481 spec.Source = &swarmapi.Privileges_CredentialSpec_File{ 482 File: c.File, 483 } 484 case c.Registry != "": 485 spec.Source = &swarmapi.Privileges_CredentialSpec_Registry{ 486 Registry: c.Registry, 487 } 488 } 489 490 return spec, nil 491 } 492 493 func healthConfigFromGRPC(h *swarmapi.HealthConfig) *container.HealthConfig { 494 interval, _ := gogotypes.DurationFromProto(h.Interval) 495 timeout, _ := gogotypes.DurationFromProto(h.Timeout) 496 startPeriod, _ := gogotypes.DurationFromProto(h.StartPeriod) 497 startInterval, _ := gogotypes.DurationFromProto(h.StartInterval) 498 return &container.HealthConfig{ 499 Test: h.Test, 500 Interval: interval, 501 Timeout: timeout, 502 Retries: int(h.Retries), 503 StartPeriod: startPeriod, 504 StartInterval: startInterval, 505 } 506 } 507 508 func healthConfigToGRPC(h *container.HealthConfig) *swarmapi.HealthConfig { 509 return &swarmapi.HealthConfig{ 510 Test: h.Test, 511 Interval: gogotypes.DurationProto(h.Interval), 512 Timeout: gogotypes.DurationProto(h.Timeout), 513 Retries: int32(h.Retries), 514 StartPeriod: gogotypes.DurationProto(h.StartPeriod), 515 StartInterval: gogotypes.DurationProto(h.StartInterval), 516 } 517 } 518 519 // IsolationFromGRPC converts a swarm api container isolation to a moby isolation representation 520 func IsolationFromGRPC(i swarmapi.ContainerSpec_Isolation) container.Isolation { 521 switch i { 522 case swarmapi.ContainerIsolationHyperV: 523 return container.IsolationHyperV 524 case swarmapi.ContainerIsolationProcess: 525 return container.IsolationProcess 526 case swarmapi.ContainerIsolationDefault: 527 return container.IsolationDefault 528 } 529 return container.IsolationEmpty 530 } 531 532 func isolationToGRPC(i container.Isolation) swarmapi.ContainerSpec_Isolation { 533 if i.IsHyperV() { 534 return swarmapi.ContainerIsolationHyperV 535 } 536 if i.IsProcess() { 537 return swarmapi.ContainerIsolationProcess 538 } 539 return swarmapi.ContainerIsolationDefault 540 } 541 542 func ulimitsFromGRPC(u []*swarmapi.ContainerSpec_Ulimit) []*units.Ulimit { 543 ulimits := make([]*units.Ulimit, len(u)) 544 545 for i, ulimit := range u { 546 ulimits[i] = &units.Ulimit{ 547 Name: ulimit.Name, 548 Soft: ulimit.Soft, 549 Hard: ulimit.Hard, 550 } 551 } 552 553 return ulimits 554 } 555 556 func ulimitsToGRPC(u []*units.Ulimit) []*swarmapi.ContainerSpec_Ulimit { 557 ulimits := make([]*swarmapi.ContainerSpec_Ulimit, len(u)) 558 559 for i, ulimit := range u { 560 ulimits[i] = &swarmapi.ContainerSpec_Ulimit{ 561 Name: ulimit.Name, 562 Soft: ulimit.Soft, 563 Hard: ulimit.Hard, 564 } 565 } 566 567 return ulimits 568 }