podman

Форк
0
/
specgen.go 
1287 строк · 33.4 Кб
1
package specgenutil
2

3
import (
4
	"encoding/json"
5
	"errors"
6
	"fmt"
7
	"os"
8
	"strconv"
9
	"strings"
10
	"time"
11

12
	"github.com/containers/common/pkg/config"
13
	"github.com/containers/image/v5/manifest"
14
	"github.com/containers/podman/v5/cmd/podman/parse"
15
	"github.com/containers/podman/v5/libpod/define"
16
	"github.com/containers/podman/v5/pkg/domain/entities"
17
	envLib "github.com/containers/podman/v5/pkg/env"
18
	"github.com/containers/podman/v5/pkg/namespaces"
19
	"github.com/containers/podman/v5/pkg/specgen"
20
	systemdDefine "github.com/containers/podman/v5/pkg/systemd/define"
21
	"github.com/containers/podman/v5/pkg/util"
22
	"github.com/docker/go-units"
23
	"github.com/opencontainers/runtime-spec/specs-go"
24
	"github.com/opencontainers/selinux/go-selinux"
25
)
26

27
const (
28
	rlimitPrefix = "rlimit_"
29
)
30

31
func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU {
32
	cpu := &specs.LinuxCPU{}
33
	hasLimits := false
34

35
	if c.CPUS > 0 {
36
		period, quota := util.CoresToPeriodAndQuota(c.CPUS)
37

38
		cpu.Period = &period
39
		cpu.Quota = &quota
40
		hasLimits = true
41
	}
42
	if c.CPUShares > 0 {
43
		cpu.Shares = &c.CPUShares
44
		hasLimits = true
45
	}
46
	if c.CPUPeriod > 0 {
47
		cpu.Period = &c.CPUPeriod
48
		hasLimits = true
49
	}
50
	if c.CPUSetCPUs != "" {
51
		cpu.Cpus = c.CPUSetCPUs
52
		hasLimits = true
53
	}
54
	if c.CPUSetMems != "" {
55
		cpu.Mems = c.CPUSetMems
56
		hasLimits = true
57
	}
58
	if c.CPUQuota > 0 {
59
		cpu.Quota = &c.CPUQuota
60
		hasLimits = true
61
	}
62
	if c.CPURTPeriod > 0 {
63
		cpu.RealtimePeriod = &c.CPURTPeriod
64
		hasLimits = true
65
	}
66
	if c.CPURTRuntime > 0 {
67
		cpu.RealtimeRuntime = &c.CPURTRuntime
68
		hasLimits = true
69
	}
70

71
	if !hasLimits {
72
		return nil
73
	}
74
	return cpu
75
}
76

77
func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) {
78
	var err error
79
	io := &specs.LinuxBlockIO{}
80
	if s.ResourceLimits == nil {
81
		s.ResourceLimits = &specs.LinuxResources{}
82
	}
83
	hasLimits := false
84
	if b := c.BlkIOWeight; len(b) > 0 {
85
		if s.ResourceLimits.BlockIO == nil {
86
			s.ResourceLimits.BlockIO = &specs.LinuxBlockIO{}
87
		}
88
		u, err := strconv.ParseUint(b, 10, 16)
89
		if err != nil {
90
			return nil, fmt.Errorf("invalid value for blkio-weight: %w", err)
91
		}
92
		nu := uint16(u)
93
		io.Weight = &nu
94
		s.ResourceLimits.BlockIO.Weight = &nu
95
		hasLimits = true
96
	}
97

98
	if len(c.BlkIOWeightDevice) > 0 {
99
		if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil {
100
			return nil, err
101
		}
102
		hasLimits = true
103
	}
104

105
	if bps := c.DeviceReadBPs; len(bps) > 0 {
106
		if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
107
			return nil, err
108
		}
109
		hasLimits = true
110
	}
111

112
	if bps := c.DeviceWriteBPs; len(bps) > 0 {
113
		if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
114
			return nil, err
115
		}
116
		hasLimits = true
117
	}
118

119
	if iops := c.DeviceReadIOPs; len(iops) > 0 {
120
		if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
121
			return nil, err
122
		}
123
		hasLimits = true
124
	}
125

126
	if iops := c.DeviceWriteIOPs; len(iops) > 0 {
127
		if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
128
			return nil, err
129
		}
130
		hasLimits = true
131
	}
132

133
	if !hasLimits {
134
		return nil, nil
135
	}
136
	return io, nil
137
}
138

139
func LimitToSwap(memory *specs.LinuxMemory, swap string, ml int64) {
140
	if ml > 0 {
141
		memory.Limit = &ml
142
		if swap == "" {
143
			limit := 2 * ml
144
			memory.Swap = &(limit)
145
		}
146
	}
147
}
148

149
func getMemoryLimits(c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) {
150
	var err error
151
	memory := &specs.LinuxMemory{}
152
	hasLimits := false
153
	if m := c.Memory; len(m) > 0 {
154
		ml, err := units.RAMInBytes(m)
155
		if err != nil {
156
			return nil, fmt.Errorf("invalid value for memory: %w", err)
157
		}
158
		LimitToSwap(memory, c.MemorySwap, ml)
159
		hasLimits = true
160
	}
161
	if m := c.MemoryReservation; len(m) > 0 {
162
		mr, err := units.RAMInBytes(m)
163
		if err != nil {
164
			return nil, fmt.Errorf("invalid value for memory: %w", err)
165
		}
166
		memory.Reservation = &mr
167
		hasLimits = true
168
	}
169
	if m := c.MemorySwap; len(m) > 0 {
170
		var ms int64
171
		// only set memory swap if it was set
172
		// -1 indicates unlimited
173
		if m != "-1" {
174
			ms, err = units.RAMInBytes(m)
175
			memory.Swap = &ms
176
			if err != nil {
177
				return nil, fmt.Errorf("invalid value for memory: %w", err)
178
			}
179
			hasLimits = true
180
		}
181
	}
182
	if c.MemorySwappiness >= 0 {
183
		swappiness := uint64(c.MemorySwappiness)
184
		memory.Swappiness = &swappiness
185
		hasLimits = true
186
	}
187
	if c.OOMKillDisable {
188
		memory.DisableOOMKiller = &c.OOMKillDisable
189
		hasLimits = true
190
	}
191
	if !hasLimits {
192
		return nil, nil
193
	}
194
	return memory, nil
195
}
196

197
func setNamespaces(rtc *config.Config, s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) error {
198
	var err error
199

200
	if c.PID != "" {
201
		s.PidNS, err = specgen.ParseNamespace(c.PID)
202
		if err != nil {
203
			return err
204
		}
205
	}
206
	if c.IPC != "" {
207
		s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC)
208
		if err != nil {
209
			return err
210
		}
211
	}
212
	if c.UTS != "" {
213
		s.UtsNS, err = specgen.ParseNamespace(c.UTS)
214
		if err != nil {
215
			return err
216
		}
217
	}
218
	if c.CgroupNS != "" {
219
		s.CgroupNS, err = specgen.ParseNamespace(c.CgroupNS)
220
		if err != nil {
221
			return err
222
		}
223
	}
224
	userns := c.UserNS
225
	if userns == "" && c.Pod == "" {
226
		if ns, ok := os.LookupEnv("PODMAN_USERNS"); ok {
227
			userns = ns
228
		} else {
229
			// TODO: This should be moved into pkg/specgen/generate so we don't use the client's containers.conf
230
			userns = rtc.Containers.UserNS
231
		}
232
	}
233
	// userns must be treated differently
234
	if userns != "" {
235
		s.UserNS, err = specgen.ParseUserNamespace(userns)
236
		if err != nil {
237
			return err
238
		}
239
	}
240
	if c.Net != nil {
241
		s.NetNS = c.Net.Network
242
	}
243

244
	if s.IDMappings == nil {
245
		userNS := namespaces.UsernsMode(s.UserNS.NSMode)
246
		tempIDMap, err := util.ParseIDMapping(namespaces.UsernsMode(userns), []string{}, []string{}, "", "")
247
		if err != nil {
248
			return err
249
		}
250
		s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
251
		if err != nil {
252
			return err
253
		}
254
		if len(s.IDMappings.GIDMap) == 0 {
255
			s.IDMappings.AutoUserNsOpts.AdditionalGIDMappings = tempIDMap.AutoUserNsOpts.AdditionalGIDMappings
256
			if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
257
				s.IDMappings.AutoUserNs = true
258
			}
259
		}
260
		if len(s.IDMappings.UIDMap) == 0 {
261
			s.IDMappings.AutoUserNsOpts.AdditionalUIDMappings = tempIDMap.AutoUserNsOpts.AdditionalUIDMappings
262
			if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
263
				s.IDMappings.AutoUserNs = true
264
			}
265
		}
266
		if tempIDMap.AutoUserNsOpts.Size != 0 {
267
			s.IDMappings.AutoUserNsOpts.Size = tempIDMap.AutoUserNsOpts.Size
268
		}
269
		// If some mappings are specified, assume a private user namespace
270
		if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
271
			s.UserNS.NSMode = specgen.Private
272
		} else {
273
			s.UserNS.NSMode = specgen.NamespaceMode(userNS)
274
		}
275
	}
276

277
	return nil
278
}
279

280
func GenRlimits(ulimits []string) ([]specs.POSIXRlimit, error) {
281
	rlimits := make([]specs.POSIXRlimit, 0, len(ulimits))
282
	// Rlimits/Ulimits
283
	for _, ulimit := range ulimits {
284
		if ulimit == "host" {
285
			rlimits = nil
286
			break
287
		}
288
		// `ulimitNameMapping` from go-units uses lowercase and names
289
		// without prefixes, e.g. `RLIMIT_NOFILE` should be converted to `nofile`.
290
		// https://github.com/containers/podman/issues/9803
291
		u := strings.TrimPrefix(strings.ToLower(ulimit), rlimitPrefix)
292
		ul, err := units.ParseUlimit(u)
293
		if err != nil {
294
			return nil, fmt.Errorf("ulimit option %q requires name=SOFT:HARD, failed to be parsed: %w", u, err)
295
		}
296
		rl := specs.POSIXRlimit{
297
			Type: ul.Name,
298
			Hard: uint64(ul.Hard),
299
			Soft: uint64(ul.Soft),
300
		}
301
		rlimits = append(rlimits, rl)
302
	}
303
	return rlimits, nil
304
}
305

306
func currentLabelOpts() ([]string, error) {
307
	label, err := selinux.CurrentLabel()
308
	if err != nil {
309
		return nil, err
310
	}
311
	if label == "" {
312
		return nil, nil
313
	}
314
	con, err := selinux.NewContext(label)
315
	if err != nil {
316
		return nil, err
317
	}
318
	return []string{
319
		fmt.Sprintf("label=user:%s", con["user"]),
320
		fmt.Sprintf("label=role:%s", con["role"]),
321
	}, nil
322
}
323

324
func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error {
325
	rtc, err := config.Default()
326
	if err != nil {
327
		return err
328
	}
329

330
	// TODO: This needs to move into pkg/specgen/generate so we aren't using containers.conf on the client.
331
	if rtc.Containers.EnableLabeledUsers {
332
		defSecurityOpts, err := currentLabelOpts()
333
		if err != nil {
334
			return err
335
		}
336

337
		c.SecurityOpt = append(defSecurityOpts, c.SecurityOpt...)
338
	}
339

340
	// validate flags as needed
341
	if err := validate(c); err != nil {
342
		return err
343
	}
344
	s.User = c.User
345
	var inputCommand []string
346
	if !c.IsInfra {
347
		if len(args) > 1 {
348
			inputCommand = args[1:]
349
		}
350
	}
351

352
	if len(c.HealthCmd) > 0 {
353
		if c.NoHealthCheck {
354
			return errors.New("cannot specify both --no-healthcheck and --health-cmd")
355
		}
356
		s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false)
357
		if err != nil {
358
			return err
359
		}
360
	} else if c.NoHealthCheck {
361
		s.HealthConfig = &manifest.Schema2HealthConfig{
362
			Test: []string{"NONE"},
363
		}
364
	}
365

366
	onFailureAction, err := define.ParseHealthCheckOnFailureAction(c.HealthOnFailure)
367
	if err != nil {
368
		return err
369
	}
370
	s.HealthCheckOnFailureAction = onFailureAction
371

372
	if c.StartupHCCmd != "" {
373
		if c.NoHealthCheck {
374
			return errors.New("cannot specify both --no-healthcheck and --health-startup-cmd")
375
		}
376
		// The hardcoded "1s" will be discarded, as the startup
377
		// healthcheck does not have a period. So just hardcode
378
		// something that parses correctly.
379
		tmpHcConfig, err := makeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true)
380
		if err != nil {
381
			return err
382
		}
383
		s.StartupHealthConfig = new(define.StartupHealthCheck)
384
		s.StartupHealthConfig.Test = tmpHcConfig.Test
385
		s.StartupHealthConfig.Interval = tmpHcConfig.Interval
386
		s.StartupHealthConfig.Timeout = tmpHcConfig.Timeout
387
		s.StartupHealthConfig.Retries = tmpHcConfig.Retries
388
		s.StartupHealthConfig.Successes = int(c.StartupHCSuccesses)
389
	}
390

391
	if err := setNamespaces(rtc, s, c); err != nil {
392
		return err
393
	}
394

395
	if s.Terminal == nil {
396
		s.Terminal = &c.TTY
397
	}
398

399
	if err := verifyExpose(c.Expose); err != nil {
400
		return err
401
	}
402
	// We are not handling the Expose flag yet.
403
	// s.PortsExpose = c.Expose
404
	if c.Net != nil {
405
		s.PortMappings = c.Net.PublishPorts
406
	}
407
	if s.PublishExposedPorts == nil {
408
		s.PublishExposedPorts = &c.PublishAll
409
	}
410

411
	if len(s.Pod) == 0 || len(c.Pod) > 0 {
412
		s.Pod = c.Pod
413
	}
414

415
	if len(c.PodIDFile) > 0 {
416
		if len(s.Pod) > 0 {
417
			return errors.New("cannot specify both --pod and --pod-id-file")
418
		}
419
		podID, err := ReadPodIDFile(c.PodIDFile)
420
		if err != nil {
421
			return err
422
		}
423
		s.Pod = podID
424
	}
425

426
	expose, err := CreateExpose(c.Expose)
427
	if err != nil {
428
		return err
429
	}
430

431
	if len(s.Expose) == 0 {
432
		s.Expose = expose
433
	}
434

435
	if sig := c.StopSignal; len(sig) > 0 {
436
		stopSignal, err := util.ParseSignal(sig)
437
		if err != nil {
438
			return err
439
		}
440
		s.StopSignal = &stopSignal
441
	}
442

443
	// ENVIRONMENT VARIABLES
444
	//
445
	// Precedence order (higher index wins):
446
	//  1) containers.conf (EnvHost, EnvHTTP, Env) 2) image data, 3 User EnvHost/EnvHTTP, 4) env-file, 5) env
447
	// containers.conf handled and image data handled on the server side
448
	// user specified EnvHost and EnvHTTP handled on Server Side relative to Server
449
	// env-file and env handled on client side
450
	var env map[string]string
451

452
	// First transform the os env into a map. We need it for the labels later in
453
	// any case.
454
	osEnv := envLib.Map(os.Environ())
455

456
	if s.EnvHost == nil {
457
		s.EnvHost = &c.EnvHost
458
	}
459

460
	if s.HTTPProxy == nil {
461
		s.HTTPProxy = &c.HTTPProxy
462
	}
463

464
	// env-file overrides any previous variables
465
	for _, f := range c.EnvFile {
466
		fileEnv, err := envLib.ParseFile(f)
467
		if err != nil {
468
			return err
469
		}
470
		// File env is overridden by env.
471
		env = envLib.Join(env, fileEnv)
472
	}
473

474
	parsedEnv, err := envLib.ParseSlice(c.Env)
475
	if err != nil {
476
		return err
477
	}
478

479
	if len(s.Env) == 0 {
480
		s.Env = envLib.Join(env, parsedEnv)
481
	}
482

483
	// LABEL VARIABLES
484
	labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
485
	if err != nil {
486
		return fmt.Errorf("unable to process labels: %w", err)
487
	}
488

489
	if systemdUnit, exists := osEnv[systemdDefine.EnvVariable]; exists {
490
		labels[systemdDefine.EnvVariable] = systemdUnit
491
	}
492

493
	if len(s.Labels) == 0 {
494
		s.Labels = labels
495
	}
496

497
	// Intel RDT CAT
498
	if c.IntelRdtClosID != "" {
499
		s.IntelRdt = &specs.LinuxIntelRdt{}
500
		s.IntelRdt.ClosID = c.IntelRdtClosID
501
	}
502

503
	// ANNOTATIONS
504
	annotations := make(map[string]string)
505

506
	// Last, add user annotations
507
	for _, annotation := range c.Annotation {
508
		key, val, hasVal := strings.Cut(annotation, "=")
509
		if !hasVal {
510
			return errors.New("annotations must be formatted KEY=VALUE")
511
		}
512
		annotations[key] = val
513
	}
514
	if len(s.Annotations) == 0 {
515
		s.Annotations = annotations
516
	}
517

518
	if len(c.StorageOpts) > 0 {
519
		opts := make(map[string]string, len(c.StorageOpts))
520
		for _, opt := range c.StorageOpts {
521
			key, val, hasVal := strings.Cut(opt, "=")
522
			if !hasVal {
523
				return errors.New("storage-opt must be formatted KEY=VALUE")
524
			}
525
			opts[key] = val
526
		}
527
		s.StorageOpts = opts
528
	}
529
	if len(s.WorkDir) == 0 {
530
		s.WorkDir = c.Workdir
531
	}
532
	if c.Entrypoint != nil {
533
		entrypoint := []string{}
534
		// Check if entrypoint specified is json
535
		if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil {
536
			entrypoint = append(entrypoint, *c.Entrypoint)
537
		}
538
		s.Entrypoint = entrypoint
539
	}
540

541
	// Include the command used to create the container.
542

543
	if len(s.ContainerCreateCommand) == 0 {
544
		s.ContainerCreateCommand = os.Args
545
	}
546

547
	if len(inputCommand) > 0 {
548
		s.Command = inputCommand
549
	}
550

551
	// SHM Size
552
	if c.ShmSize != "" {
553
		val, err := units.RAMInBytes(c.ShmSize)
554

555
		if err != nil {
556
			return fmt.Errorf("unable to translate --shm-size: %w", err)
557
		}
558

559
		s.ShmSize = &val
560
	}
561

562
	// SHM Size Systemd
563
	if c.ShmSizeSystemd != "" {
564
		val, err := units.RAMInBytes(c.ShmSizeSystemd)
565
		if err != nil {
566
			return fmt.Errorf("unable to translate --shm-size-systemd: %w", err)
567
		}
568

569
		s.ShmSizeSystemd = &val
570
	}
571

572
	if c.Net != nil {
573
		s.Networks = c.Net.Networks
574
	}
575

576
	if c.Net != nil {
577
		s.HostAdd = c.Net.AddHosts
578
		s.UseImageResolvConf = &c.Net.UseImageResolvConf
579
		s.DNSServers = c.Net.DNSServers
580
		s.DNSSearch = c.Net.DNSSearch
581
		s.DNSOptions = c.Net.DNSOptions
582
		s.NetworkOptions = c.Net.NetworkOptions
583
		s.UseImageHosts = &c.Net.NoHosts
584
	}
585
	if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 {
586
		s.HostUsers = c.HostUsers
587
	}
588
	if len(c.ImageVolume) != 0 {
589
		if len(s.ImageVolumeMode) == 0 {
590
			s.ImageVolumeMode = c.ImageVolume
591
		}
592
	}
593
	if s.ImageVolumeMode == define.TypeBind {
594
		s.ImageVolumeMode = "anonymous"
595
	}
596

597
	if len(s.Systemd) == 0 || len(c.Systemd) != 0 {
598
		s.Systemd = strings.ToLower(c.Systemd)
599
	}
600
	if len(s.SdNotifyMode) == 0 || len(c.SdNotifyMode) != 0 {
601
		s.SdNotifyMode = c.SdNotifyMode
602
	}
603
	if s.ResourceLimits == nil {
604
		s.ResourceLimits = &specs.LinuxResources{}
605
	}
606

607
	s.ResourceLimits, err = GetResources(s, c)
608
	if err != nil {
609
		return err
610
	}
611

612
	if s.LogConfiguration == nil {
613
		s.LogConfiguration = &specgen.LogConfig{}
614
	}
615

616
	if ld := c.LogDriver; len(ld) > 0 {
617
		s.LogConfiguration.Driver = ld
618
	}
619
	if len(s.CgroupParent) == 0 || len(c.CgroupParent) != 0 {
620
		s.CgroupParent = c.CgroupParent
621
	}
622
	if len(s.CgroupsMode) == 0 {
623
		s.CgroupsMode = c.CgroupsMode
624
	}
625

626
	if len(s.Groups) == 0 || len(c.GroupAdd) != 0 {
627
		s.Groups = c.GroupAdd
628
	}
629

630
	if len(s.Hostname) == 0 || len(c.Hostname) != 0 {
631
		s.Hostname = c.Hostname
632
	}
633
	sysctl := map[string]string{}
634
	if ctl := c.Sysctl; len(ctl) > 0 {
635
		sysctl, err = util.ValidateSysctls(ctl)
636
		if err != nil {
637
			return err
638
		}
639
	}
640
	if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 {
641
		s.Sysctl = sysctl
642
	}
643

644
	if len(s.CapAdd) == 0 || len(c.CapAdd) != 0 {
645
		s.CapAdd = c.CapAdd
646
	}
647
	if len(s.CapDrop) == 0 || len(c.CapDrop) != 0 {
648
		s.CapDrop = c.CapDrop
649
	}
650
	if s.Privileged == nil {
651
		s.Privileged = &c.Privileged
652
	}
653
	if s.ReadOnlyFilesystem == nil {
654
		s.ReadOnlyFilesystem = &c.ReadOnly
655
	}
656
	if len(s.ConmonPidFile) == 0 || len(c.ConmonPIDFile) != 0 {
657
		s.ConmonPidFile = c.ConmonPIDFile
658
	}
659

660
	if len(s.DependencyContainers) == 0 || len(c.Requires) != 0 {
661
		s.DependencyContainers = c.Requires
662
	}
663

664
	// Only add ReadWrite tmpfs mounts iff the container is
665
	// being run ReadOnly and ReadWriteTmpFS is not disabled,
666
	// (user specifying --read-only-tmpfs=false.)
667
	localRWTmpfs := c.ReadOnly && c.ReadWriteTmpFS
668
	s.ReadWriteTmpfs = &localRWTmpfs
669

670
	//  TODO convert to map?
671
	// check if key=value and convert
672
	sysmap := make(map[string]string)
673
	for _, ctl := range c.Sysctl {
674
		key, val, hasVal := strings.Cut(ctl, "=")
675
		if !hasVal {
676
			return fmt.Errorf("invalid sysctl value %q", ctl)
677
		}
678
		sysmap[key] = val
679
	}
680
	if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 {
681
		s.Sysctl = sysmap
682
	}
683

684
	if c.CIDFile != "" {
685
		s.Annotations[define.InspectAnnotationCIDFile] = c.CIDFile
686
	}
687

688
	for _, opt := range c.SecurityOpt {
689
		// Docker deprecated the ":" syntax but still supports it,
690
		// so we need to as well
691
		var key, val string
692
		var hasVal bool
693
		if strings.Contains(opt, "=") {
694
			key, val, hasVal = strings.Cut(opt, "=")
695
		} else {
696
			key, val, hasVal = strings.Cut(opt, ":")
697
		}
698
		if !hasVal &&
699
			key != "no-new-privileges" {
700
			return fmt.Errorf("invalid --security-opt 1: %q", opt)
701
		}
702
		switch key {
703
		case "apparmor":
704
			s.ContainerSecurityConfig.ApparmorProfile = val
705
			s.Annotations[define.InspectAnnotationApparmor] = val
706
		case "label":
707
			if val == "nested" {
708
				localTrue := true
709
				s.ContainerSecurityConfig.LabelNested = &localTrue
710
				continue
711
			}
712
			// TODO selinux opts and label opts are the same thing
713
			s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, val)
714
			s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
715
		case "mask":
716
			s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(val, ":")...)
717
		case "proc-opts":
718
			s.ProcOpts = strings.Split(val, ",")
719
		case "seccomp":
720
			s.SeccompProfilePath = val
721
			s.Annotations[define.InspectAnnotationSeccomp] = val
722
			// this option is for docker compatibility, it is the same as unmask=ALL
723
		case "systempaths":
724
			if val == "unconfined" {
725
				s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...)
726
			} else {
727
				return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", val)
728
			}
729
		case "unmask":
730
			if hasVal {
731
				s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, val)
732
			}
733
		case "no-new-privileges":
734
			noNewPrivileges := true
735
			if hasVal {
736
				noNewPrivileges, err = strconv.ParseBool(val)
737
				if err != nil {
738
					return fmt.Errorf("invalid --security-opt 2: %q", opt)
739
				}
740
			}
741
			s.ContainerSecurityConfig.NoNewPrivileges = &noNewPrivileges
742
		default:
743
			return fmt.Errorf("invalid --security-opt 2: %q", opt)
744
		}
745
	}
746

747
	if len(s.SeccompPolicy) == 0 || len(c.SeccompPolicy) != 0 {
748
		s.SeccompPolicy = c.SeccompPolicy
749
	}
750

751
	if len(s.VolumesFrom) == 0 || len(c.VolumesFrom) != 0 {
752
		s.VolumesFrom = c.VolumesFrom
753
	}
754

755
	// Only add read-only tmpfs mounts in case that we are read-only and the
756
	// read-only tmpfs flag has been set.
757
	mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(rtc, c.Volume, c.Mount, c.TmpFS)
758
	if err != nil {
759
		return err
760
	}
761
	if len(s.Mounts) == 0 || len(c.Mount) != 0 {
762
		s.Mounts = mounts
763
	}
764
	if len(s.Volumes) == 0 || len(c.Volume) != 0 {
765
		s.Volumes = volumes
766
	}
767

768
	if s.LabelNested != nil && *s.LabelNested {
769
		// Need to unmask the SELinux file system
770
		s.Unmask = append(s.Unmask, "/sys/fs/selinux", "/proc")
771
		s.Mounts = append(s.Mounts, specs.Mount{
772
			Source:      "/sys/fs/selinux",
773
			Destination: "/sys/fs/selinux",
774
			Type:        define.TypeBind,
775
		})
776
		s.Annotations[define.RunOCIMountContextType] = "rootcontext"
777
	}
778
	// TODO make sure these work in clone
779
	if len(s.OverlayVolumes) == 0 {
780
		s.OverlayVolumes = overlayVolumes
781
	}
782
	if len(s.ImageVolumes) == 0 {
783
		s.ImageVolumes = imageVolumes
784
	}
785

786
	devices := c.Devices
787
	for _, gpu := range c.GPUs {
788
		devices = append(devices, "nvidia.com/gpu="+gpu)
789
	}
790

791
	for _, dev := range devices {
792
		s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
793
	}
794

795
	for _, rule := range c.DeviceCgroupRule {
796
		dev, err := parseLinuxResourcesDeviceAccess(rule)
797
		if err != nil {
798
			return err
799
		}
800
		s.DeviceCgroupRule = append(s.DeviceCgroupRule, dev)
801
	}
802

803
	if s.Init == nil {
804
		s.Init = &c.Init
805
	}
806
	if len(s.InitPath) == 0 || len(c.InitPath) != 0 {
807
		s.InitPath = c.InitPath
808
	}
809
	if s.Stdin == nil {
810
		s.Stdin = &c.Interactive
811
	}
812
	// quiet
813
	// DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
814

815
	// Rlimits/Ulimits
816
	s.Rlimits, err = GenRlimits(c.Ulimit)
817
	if err != nil {
818
		return err
819
	}
820

821
	logOpts := make(map[string]string)
822
	for _, o := range c.LogOptions {
823
		key, val, hasVal := strings.Cut(o, "=")
824
		if !hasVal {
825
			return fmt.Errorf("invalid log option %q", o)
826
		}
827
		switch strings.ToLower(key) {
828
		case "driver":
829
			s.LogConfiguration.Driver = val
830
		case "path":
831
			s.LogConfiguration.Path = val
832
		case "max-size":
833
			logSize, err := units.FromHumanSize(val)
834
			if err != nil {
835
				return err
836
			}
837
			s.LogConfiguration.Size = logSize
838
		default:
839
			logOpts[key] = val
840
		}
841
	}
842
	if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 {
843
		s.LogConfiguration.Options = logOpts
844
	}
845
	if len(s.Name) == 0 || len(c.Name) != 0 {
846
		s.Name = c.Name
847
	}
848

849
	if c.PreserveFDs != 0 && c.PreserveFD != nil {
850
		return errors.New("cannot specify both --preserve-fds and --preserve-fd")
851
	}
852

853
	if s.PreserveFDs == 0 || c.PreserveFDs != 0 {
854
		s.PreserveFDs = c.PreserveFDs
855
	}
856
	if s.PreserveFD == nil || c.PreserveFD != nil {
857
		s.PreserveFD = c.PreserveFD
858
	}
859

860
	if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil {
861
		s.OOMScoreAdj = c.OOMScoreAdj
862
	}
863
	if c.Restart != "" {
864
		policy, retries, err := util.ParseRestartPolicy(c.Restart)
865
		if err != nil {
866
			return err
867
		}
868
		s.RestartPolicy = policy
869
		s.RestartRetries = &retries
870
	}
871

872
	if len(s.Secrets) == 0 || len(c.Secrets) != 0 {
873
		s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets)
874
		if err != nil {
875
			return err
876
		}
877
	}
878

879
	if c.Personality != "" {
880
		s.Personality = &specs.LinuxPersonality{}
881
		s.Personality.Domain = specs.LinuxPersonalityDomain(c.Personality)
882
	}
883

884
	if s.Remove == nil {
885
		s.Remove = &c.Rm
886
	}
887
	if s.StopTimeout == nil || c.StopTimeout != 0 {
888
		s.StopTimeout = &c.StopTimeout
889
	}
890
	if s.Timeout == 0 || c.Timeout != 0 {
891
		s.Timeout = c.Timeout
892
	}
893
	if len(s.Timezone) == 0 || len(c.Timezone) != 0 {
894
		s.Timezone = c.Timezone
895
	}
896
	if len(s.Umask) == 0 || len(c.Umask) != 0 {
897
		s.Umask = c.Umask
898
	}
899
	if len(s.PidFile) == 0 || len(c.PidFile) != 0 {
900
		s.PidFile = c.PidFile
901
	}
902
	if s.Volatile == nil {
903
		s.Volatile = &c.Rm
904
	}
905
	if len(s.EnvMerge) == 0 || len(c.EnvMerge) != 0 {
906
		s.EnvMerge = c.EnvMerge
907
	}
908
	if len(s.UnsetEnv) == 0 || len(c.UnsetEnv) != 0 {
909
		s.UnsetEnv = c.UnsetEnv
910
	}
911
	if s.UnsetEnvAll == nil {
912
		s.UnsetEnvAll = &c.UnsetEnvAll
913
	}
914
	if len(s.ChrootDirs) == 0 || len(c.ChrootDirs) != 0 {
915
		s.ChrootDirs = c.ChrootDirs
916
	}
917

918
	// Initcontainers
919
	if len(s.InitContainerType) == 0 || len(c.InitContainerType) != 0 {
920
		s.InitContainerType = c.InitContainerType
921
	}
922

923
	t := true
924
	if s.Passwd == nil {
925
		s.Passwd = &t
926
	}
927

928
	if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 {
929
		s.PasswdEntry = c.PasswdEntry
930
	}
931

932
	if len(s.GroupEntry) == 0 || len(c.GroupEntry) != 0 {
933
		s.GroupEntry = c.GroupEntry
934
	}
935

936
	return nil
937
}
938

939
func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) {
940
	cmdArr := []string{}
941
	isArr := true
942
	err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling
943
	if err != nil {
944
		cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat
945
		isArr = false
946
	}
947
	// Every healthcheck requires a command
948
	if len(cmdArr) == 0 {
949
		return nil, errors.New("must define a healthcheck command for all healthchecks")
950
	}
951

952
	var concat string
953
	if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestCmd || strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // this is for compat, we are already split properly for most compat cases
954
		cmdArr = strings.Fields(inCmd)
955
	} else if strings.ToUpper(cmdArr[0]) != define.HealthConfigTestCmdShell { // this is for podman side of things, won't contain the keywords
956
		if isArr && len(cmdArr) > 1 { // an array of consecutive commands
957
			cmdArr = append([]string{define.HealthConfigTestCmd}, cmdArr...)
958
		} else { // one singular command
959
			if len(cmdArr) == 1 {
960
				concat = cmdArr[0]
961
			} else {
962
				concat = strings.Join(cmdArr[0:], " ")
963
			}
964
			cmdArr = append([]string{define.HealthConfigTestCmdShell}, concat)
965
		}
966
	}
967

968
	if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // if specified to remove healtcheck
969
		cmdArr = []string{define.HealthConfigTestNone}
970
	}
971

972
	// healthcheck is by default an array, so we simply pass the user input
973
	hc := manifest.Schema2HealthConfig{
974
		Test: cmdArr,
975
	}
976

977
	if interval == "disable" {
978
		interval = "0"
979
	}
980
	intervalDuration, err := time.ParseDuration(interval)
981
	if err != nil {
982
		return nil, fmt.Errorf("invalid healthcheck-interval: %w", err)
983
	}
984

985
	hc.Interval = intervalDuration
986

987
	if retries < 1 && !isStartup {
988
		return nil, errors.New("healthcheck-retries must be greater than 0")
989
	}
990
	hc.Retries = int(retries)
991
	timeoutDuration, err := time.ParseDuration(timeout)
992
	if err != nil {
993
		return nil, fmt.Errorf("invalid healthcheck-timeout: %w", err)
994
	}
995
	if timeoutDuration < time.Duration(1) {
996
		return nil, errors.New("healthcheck-timeout must be at least 1 second")
997
	}
998
	hc.Timeout = timeoutDuration
999

1000
	startPeriodDuration, err := time.ParseDuration(startPeriod)
1001
	if err != nil {
1002
		return nil, fmt.Errorf("invalid healthcheck-start-period: %w", err)
1003
	}
1004
	if startPeriodDuration < time.Duration(0) {
1005
		return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
1006
	}
1007
	hc.StartPeriod = startPeriodDuration
1008

1009
	return &hc, nil
1010
}
1011

1012
func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) {
1013
	wd := make(map[string]specs.LinuxWeightDevice)
1014
	for _, dev := range weightDevs {
1015
		key, val, hasVal := strings.Cut(dev, ":")
1016
		if !hasVal {
1017
			return nil, fmt.Errorf("bad format: %s", dev)
1018
		}
1019
		if !strings.HasPrefix(key, "/dev/") {
1020
			return nil, fmt.Errorf("bad format for device path: %s", dev)
1021
		}
1022
		weight, err := strconv.ParseUint(val, 10, 0)
1023
		if err != nil {
1024
			return nil, fmt.Errorf("invalid weight for device: %s", dev)
1025
		}
1026
		if weight > 0 && (weight < 10 || weight > 1000) {
1027
			return nil, fmt.Errorf("invalid weight for device: %s", dev)
1028
		}
1029
		w := uint16(weight)
1030
		wd[key] = specs.LinuxWeightDevice{
1031
			Weight:     &w,
1032
			LeafWeight: nil,
1033
		}
1034
	}
1035
	return wd, nil
1036
}
1037

1038
func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
1039
	td := make(map[string]specs.LinuxThrottleDevice)
1040
	for _, dev := range bpsDevices {
1041
		key, val, hasVal := strings.Cut(dev, ":")
1042
		if !hasVal {
1043
			return nil, fmt.Errorf("bad format: %s", dev)
1044
		}
1045
		if !strings.HasPrefix(key, "/dev/") {
1046
			return nil, fmt.Errorf("bad format for device path: %s", dev)
1047
		}
1048
		rate, err := units.RAMInBytes(val)
1049
		if err != nil {
1050
			return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", dev)
1051
		}
1052
		if rate < 0 {
1053
			return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", dev)
1054
		}
1055
		td[key] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
1056
	}
1057
	return td, nil
1058
}
1059

1060
func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
1061
	td := make(map[string]specs.LinuxThrottleDevice)
1062
	for _, dev := range iopsDevices {
1063
		key, val, hasVal := strings.Cut(dev, ":")
1064
		if !hasVal {
1065
			return nil, fmt.Errorf("bad format: %s", dev)
1066
		}
1067
		if !strings.HasPrefix(key, "/dev/") {
1068
			return nil, fmt.Errorf("bad format for device path: %s", dev)
1069
		}
1070
		rate, err := strconv.ParseUint(val, 10, 64)
1071
		if err != nil {
1072
			return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", dev)
1073
		}
1074
		td[key] = specs.LinuxThrottleDevice{Rate: rate}
1075
	}
1076
	return td, nil
1077
}
1078

1079
func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) {
1080
	secretParseError := errors.New("parsing secret")
1081
	var mount []specgen.Secret
1082
	envs := make(map[string]string)
1083
	for _, val := range secrets {
1084
		// mount only tells if user has set an option that can only be used with mount secret type
1085
		mountOnly := false
1086
		source := ""
1087
		secretType := ""
1088
		target := ""
1089
		var uid, gid uint32
1090
		// default mode 444 octal = 292 decimal
1091
		var mode uint32 = 292
1092
		split := strings.Split(val, ",")
1093

1094
		// --secret mysecret
1095
		if len(split) == 1 {
1096
			mountSecret := specgen.Secret{
1097
				Source: val,
1098
				Target: target,
1099
				UID:    uid,
1100
				GID:    gid,
1101
				Mode:   mode,
1102
			}
1103
			mount = append(mount, mountSecret)
1104
			continue
1105
		}
1106
		// --secret mysecret,opt=opt
1107
		if !strings.Contains(split[0], "=") {
1108
			source = split[0]
1109
			split = split[1:]
1110
		}
1111

1112
		for _, val := range split {
1113
			name, value, hasValue := strings.Cut(val, "=")
1114
			if !hasValue {
1115
				return nil, nil, fmt.Errorf("option %s must be in form option=value: %w", val, secretParseError)
1116
			}
1117
			switch name {
1118
			case "source":
1119
				source = value
1120
			case "type":
1121
				if secretType != "" {
1122
					return nil, nil, fmt.Errorf("cannot set more than one secret type: %w", secretParseError)
1123
				}
1124
				if value != "mount" && value != "env" {
1125
					return nil, nil, fmt.Errorf("type %s is invalid: %w", value, secretParseError)
1126
				}
1127
				secretType = value
1128
			case "target":
1129
				target = value
1130
			case "mode":
1131
				mountOnly = true
1132
				mode64, err := strconv.ParseUint(value, 8, 32)
1133
				if err != nil {
1134
					return nil, nil, fmt.Errorf("mode %s invalid: %w", value, secretParseError)
1135
				}
1136
				mode = uint32(mode64)
1137
			case "uid", "UID":
1138
				mountOnly = true
1139
				uid64, err := strconv.ParseUint(value, 10, 32)
1140
				if err != nil {
1141
					return nil, nil, fmt.Errorf("UID %s invalid: %w", value, secretParseError)
1142
				}
1143
				uid = uint32(uid64)
1144
			case "gid", "GID":
1145
				mountOnly = true
1146
				gid64, err := strconv.ParseUint(value, 10, 32)
1147
				if err != nil {
1148
					return nil, nil, fmt.Errorf("GID %s invalid: %w", value, secretParseError)
1149
				}
1150
				gid = uint32(gid64)
1151

1152
			default:
1153
				return nil, nil, fmt.Errorf("option %s invalid: %w", val, secretParseError)
1154
			}
1155
		}
1156

1157
		if secretType == "" {
1158
			secretType = "mount"
1159
		}
1160
		if source == "" {
1161
			return nil, nil, fmt.Errorf("no source found %s: %w", val, secretParseError)
1162
		}
1163
		if secretType == "mount" {
1164
			mountSecret := specgen.Secret{
1165
				Source: source,
1166
				Target: target,
1167
				UID:    uid,
1168
				GID:    gid,
1169
				Mode:   mode,
1170
			}
1171
			mount = append(mount, mountSecret)
1172
		}
1173
		if secretType == "env" {
1174
			if mountOnly {
1175
				return nil, nil, fmt.Errorf("UID, GID, Mode options cannot be set with secret type env: %w", secretParseError)
1176
			}
1177
			if target == "" {
1178
				target = source
1179
			}
1180
			envs[target] = source
1181
		}
1182
	}
1183
	return mount, envs, nil
1184
}
1185

1186
var cgroupDeviceType = map[string]bool{
1187
	"a": true, // all
1188
	"b": true, // block device
1189
	"c": true, // character device
1190
}
1191

1192
var cgroupDeviceAccess = map[string]bool{
1193
	"r": true, // read
1194
	"w": true, // write
1195
	"m": true, // mknod
1196
}
1197

1198
// parseLinuxResourcesDeviceAccess parses the raw string passed with the --device-access-add flag
1199
func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, error) {
1200
	var devType, access string
1201
	var major, minor *int64
1202

1203
	value := strings.Split(device, " ")
1204
	if len(value) != 3 {
1205
		return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device cgroup rule requires type, major:Minor, and access rules: %q", device)
1206
	}
1207

1208
	devType = value[0]
1209
	if !cgroupDeviceType[devType] {
1210
		return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType)
1211
	}
1212

1213
	majorNumber, minorNumber, hasMinor := strings.Cut(value[1], ":")
1214
	if majorNumber != "*" {
1215
		i, err := strconv.ParseUint(majorNumber, 10, 64)
1216
		if err != nil {
1217
			return specs.LinuxDeviceCgroup{}, err
1218
		}
1219
		m := int64(i)
1220
		major = &m
1221
	}
1222
	if hasMinor && minorNumber != "*" {
1223
		i, err := strconv.ParseUint(minorNumber, 10, 64)
1224
		if err != nil {
1225
			return specs.LinuxDeviceCgroup{}, err
1226
		}
1227
		m := int64(i)
1228
		minor = &m
1229
	}
1230
	access = value[2]
1231
	for _, c := range strings.Split(access, "") {
1232
		if !cgroupDeviceAccess[c] {
1233
			return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device access in device-access-add: %s", c)
1234
		}
1235
	}
1236
	return specs.LinuxDeviceCgroup{
1237
		Allow:  true,
1238
		Type:   devType,
1239
		Major:  major,
1240
		Minor:  minor,
1241
		Access: access,
1242
	}, nil
1243
}
1244

1245
func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxResources, error) {
1246
	var err error
1247
	if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) {
1248
		s.ResourceLimits.Memory, err = getMemoryLimits(c)
1249
		if err != nil {
1250
			return nil, err
1251
		}
1252
	}
1253
	if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) {
1254
		s.ResourceLimits.BlockIO, err = getIOLimits(s, c)
1255
		if err != nil {
1256
			return nil, err
1257
		}
1258
	}
1259
	if c.PIDsLimit != nil {
1260
		pids := specs.LinuxPids{
1261
			Limit: *c.PIDsLimit,
1262
		}
1263

1264
		s.ResourceLimits.Pids = &pids
1265
	}
1266

1267
	if s.ResourceLimits.CPU == nil || (c.CPUPeriod != 0 || c.CPUQuota != 0 || c.CPURTPeriod != 0 || c.CPURTRuntime != 0 || c.CPUS != 0 || len(c.CPUSetCPUs) != 0 || len(c.CPUSetMems) != 0 || c.CPUShares != 0) {
1268
		s.ResourceLimits.CPU = getCPULimits(c)
1269
	}
1270

1271
	unifieds := make(map[string]string)
1272
	for _, unified := range c.CgroupConf {
1273
		key, val, hasVal := strings.Cut(unified, "=")
1274
		if !hasVal {
1275
			return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE")
1276
		}
1277
		unifieds[key] = val
1278
	}
1279
	if len(unifieds) > 0 {
1280
		s.ResourceLimits.Unified = unifieds
1281
	}
1282

1283
	if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil {
1284
		s.ResourceLimits = nil
1285
	}
1286
	return s.ResourceLimits, nil
1287
}
1288

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.