github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/specgen.go (about)

     1  package specgenutil
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/containers/common/pkg/config"
    12  	"github.com/containers/image/v5/manifest"
    13  	"github.com/hanks177/podman/v4/cmd/podman/parse"
    14  	"github.com/hanks177/podman/v4/libpod/define"
    15  	ann "github.com/hanks177/podman/v4/pkg/annotations"
    16  	"github.com/hanks177/podman/v4/pkg/domain/entities"
    17  	envLib "github.com/hanks177/podman/v4/pkg/env"
    18  	"github.com/hanks177/podman/v4/pkg/namespaces"
    19  	"github.com/hanks177/podman/v4/pkg/specgen"
    20  	systemdDefine "github.com/hanks177/podman/v4/pkg/systemd/define"
    21  	"github.com/hanks177/podman/v4/pkg/util"
    22  	"github.com/docker/docker/opts"
    23  	"github.com/docker/go-units"
    24  	"github.com/opencontainers/runtime-spec/specs-go"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  func getCPULimits(c *entities.ContainerCreateOptions) *specs.LinuxCPU {
    29  	cpu := &specs.LinuxCPU{}
    30  	hasLimits := false
    31  
    32  	if c.CPUS > 0 {
    33  		period, quota := util.CoresToPeriodAndQuota(c.CPUS)
    34  
    35  		cpu.Period = &period
    36  		cpu.Quota = &quota
    37  		hasLimits = true
    38  	}
    39  	if c.CPUShares > 0 {
    40  		cpu.Shares = &c.CPUShares
    41  		hasLimits = true
    42  	}
    43  	if c.CPUPeriod > 0 {
    44  		cpu.Period = &c.CPUPeriod
    45  		hasLimits = true
    46  	}
    47  	if c.CPUSetCPUs != "" {
    48  		cpu.Cpus = c.CPUSetCPUs
    49  		hasLimits = true
    50  	}
    51  	if c.CPUSetMems != "" {
    52  		cpu.Mems = c.CPUSetMems
    53  		hasLimits = true
    54  	}
    55  	if c.CPUQuota > 0 {
    56  		cpu.Quota = &c.CPUQuota
    57  		hasLimits = true
    58  	}
    59  	if c.CPURTPeriod > 0 {
    60  		cpu.RealtimePeriod = &c.CPURTPeriod
    61  		hasLimits = true
    62  	}
    63  	if c.CPURTRuntime > 0 {
    64  		cpu.RealtimeRuntime = &c.CPURTRuntime
    65  		hasLimits = true
    66  	}
    67  
    68  	if !hasLimits {
    69  		return nil
    70  	}
    71  	return cpu
    72  }
    73  
    74  func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxBlockIO, error) {
    75  	var err error
    76  	io := &specs.LinuxBlockIO{}
    77  	hasLimits := false
    78  	if b := c.BlkIOWeight; len(b) > 0 {
    79  		u, err := strconv.ParseUint(b, 10, 16)
    80  		if err != nil {
    81  			return nil, errors.Wrapf(err, "invalid value for blkio-weight")
    82  		}
    83  		nu := uint16(u)
    84  		io.Weight = &nu
    85  		hasLimits = true
    86  	}
    87  
    88  	if len(c.BlkIOWeightDevice) > 0 {
    89  		if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil {
    90  			return nil, err
    91  		}
    92  		hasLimits = true
    93  	}
    94  
    95  	if bps := c.DeviceReadBPs; len(bps) > 0 {
    96  		if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
    97  			return nil, err
    98  		}
    99  		hasLimits = true
   100  	}
   101  
   102  	if bps := c.DeviceWriteBPs; len(bps) > 0 {
   103  		if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
   104  			return nil, err
   105  		}
   106  		hasLimits = true
   107  	}
   108  
   109  	if iops := c.DeviceReadIOPs; len(iops) > 0 {
   110  		if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
   111  			return nil, err
   112  		}
   113  		hasLimits = true
   114  	}
   115  
   116  	if iops := c.DeviceWriteIOPs; len(iops) > 0 {
   117  		if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
   118  			return nil, err
   119  		}
   120  		hasLimits = true
   121  	}
   122  
   123  	if !hasLimits {
   124  		return nil, nil
   125  	}
   126  	return io, nil
   127  }
   128  
   129  func LimitToSwap(memory *specs.LinuxMemory, swap string, ml int64) {
   130  	if ml > 0 {
   131  		memory.Limit = &ml
   132  		if swap == "" {
   133  			limit := 2 * ml
   134  			memory.Swap = &(limit)
   135  		}
   136  	}
   137  }
   138  
   139  func getMemoryLimits(c *entities.ContainerCreateOptions) (*specs.LinuxMemory, error) {
   140  	var err error
   141  	memory := &specs.LinuxMemory{}
   142  	hasLimits := false
   143  	if m := c.Memory; len(m) > 0 {
   144  		ml, err := units.RAMInBytes(m)
   145  		if err != nil {
   146  			return nil, errors.Wrapf(err, "invalid value for memory")
   147  		}
   148  		LimitToSwap(memory, c.MemorySwap, ml)
   149  		hasLimits = true
   150  	}
   151  	if m := c.MemoryReservation; len(m) > 0 {
   152  		mr, err := units.RAMInBytes(m)
   153  		if err != nil {
   154  			return nil, errors.Wrapf(err, "invalid value for memory")
   155  		}
   156  		memory.Reservation = &mr
   157  		hasLimits = true
   158  	}
   159  	if m := c.MemorySwap; len(m) > 0 {
   160  		var ms int64
   161  		// only set memory swap if it was set
   162  		// -1 indicates unlimited
   163  		if m != "-1" {
   164  			ms, err = units.RAMInBytes(m)
   165  			memory.Swap = &ms
   166  			if err != nil {
   167  				return nil, errors.Wrapf(err, "invalid value for memory")
   168  			}
   169  			hasLimits = true
   170  		}
   171  	}
   172  	if c.MemorySwappiness >= 0 {
   173  		swappiness := uint64(c.MemorySwappiness)
   174  		memory.Swappiness = &swappiness
   175  		hasLimits = true
   176  	}
   177  	if c.OOMKillDisable {
   178  		memory.DisableOOMKiller = &c.OOMKillDisable
   179  		hasLimits = true
   180  	}
   181  	if !hasLimits {
   182  		return nil, nil
   183  	}
   184  	return memory, nil
   185  }
   186  
   187  func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) error {
   188  	var err error
   189  
   190  	if c.PID != "" {
   191  		s.PidNS, err = specgen.ParseNamespace(c.PID)
   192  		if err != nil {
   193  			return err
   194  		}
   195  	}
   196  	if c.IPC != "" {
   197  		s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC)
   198  		if err != nil {
   199  			return err
   200  		}
   201  	}
   202  	if c.UTS != "" {
   203  		s.UtsNS, err = specgen.ParseNamespace(c.UTS)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  	if c.CgroupNS != "" {
   209  		s.CgroupNS, err = specgen.ParseNamespace(c.CgroupNS)
   210  		if err != nil {
   211  			return err
   212  		}
   213  	}
   214  	userns := os.Getenv("PODMAN_USERNS")
   215  	if c.UserNS != "" {
   216  		userns = c.UserNS
   217  	}
   218  	// userns must be treated differently
   219  	if userns != "" {
   220  		s.UserNS, err = specgen.ParseUserNamespace(userns)
   221  		if err != nil {
   222  			return err
   223  		}
   224  	}
   225  	if c.Net != nil {
   226  		s.NetNS = c.Net.Network
   227  	}
   228  	return nil
   229  }
   230  
   231  func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions, args []string) error {
   232  	rtc, err := config.Default()
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	// validate flags as needed
   238  	if err := validate(c); err != nil {
   239  		return err
   240  	}
   241  	s.User = c.User
   242  	var inputCommand []string
   243  	if !c.IsInfra {
   244  		if len(args) > 1 {
   245  			inputCommand = args[1:]
   246  		}
   247  	}
   248  
   249  	if len(c.HealthCmd) > 0 {
   250  		if c.NoHealthCheck {
   251  			return errors.New("Cannot specify both --no-healthcheck and --health-cmd")
   252  		}
   253  		s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
   254  		if err != nil {
   255  			return err
   256  		}
   257  	} else if c.NoHealthCheck {
   258  		s.HealthConfig = &manifest.Schema2HealthConfig{
   259  			Test: []string{"NONE"},
   260  		}
   261  	}
   262  	if err := setNamespaces(s, c); err != nil {
   263  		return err
   264  	}
   265  
   266  	if s.IDMappings == nil {
   267  		userNS := namespaces.UsernsMode(s.UserNS.NSMode)
   268  		tempIDMap, err := util.ParseIDMapping(namespaces.UsernsMode(c.UserNS), []string{}, []string{}, "", "")
   269  		if err != nil {
   270  			return err
   271  		}
   272  		s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
   273  		if err != nil {
   274  			return err
   275  		}
   276  		if len(s.IDMappings.GIDMap) == 0 {
   277  			s.IDMappings.AutoUserNsOpts.AdditionalGIDMappings = tempIDMap.AutoUserNsOpts.AdditionalGIDMappings
   278  			if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
   279  				s.IDMappings.AutoUserNs = true
   280  			}
   281  		}
   282  		if len(s.IDMappings.UIDMap) == 0 {
   283  			s.IDMappings.AutoUserNsOpts.AdditionalUIDMappings = tempIDMap.AutoUserNsOpts.AdditionalUIDMappings
   284  			if s.UserNS.NSMode == specgen.NamespaceMode("auto") {
   285  				s.IDMappings.AutoUserNs = true
   286  			}
   287  		}
   288  		if tempIDMap.AutoUserNsOpts.Size != 0 {
   289  			s.IDMappings.AutoUserNsOpts.Size = tempIDMap.AutoUserNsOpts.Size
   290  		}
   291  		// If some mappings are specified, assume a private user namespace
   292  		if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
   293  			s.UserNS.NSMode = specgen.Private
   294  		} else {
   295  			s.UserNS.NSMode = specgen.NamespaceMode(userNS)
   296  		}
   297  	}
   298  
   299  	if !s.Terminal {
   300  		s.Terminal = c.TTY
   301  	}
   302  
   303  	if err := verifyExpose(c.Expose); err != nil {
   304  		return err
   305  	}
   306  	// We are not handling the Expose flag yet.
   307  	// s.PortsExpose = c.Expose
   308  	if c.Net != nil {
   309  		s.PortMappings = c.Net.PublishPorts
   310  	}
   311  	if !s.PublishExposedPorts {
   312  		s.PublishExposedPorts = c.PublishAll
   313  	}
   314  
   315  	if len(s.Pod) == 0 {
   316  		s.Pod = c.Pod
   317  	}
   318  
   319  	if len(c.PodIDFile) > 0 {
   320  		if len(s.Pod) > 0 {
   321  			return errors.New("Cannot specify both --pod and --pod-id-file")
   322  		}
   323  		podID, err := ReadPodIDFile(c.PodIDFile)
   324  		if err != nil {
   325  			return err
   326  		}
   327  		s.Pod = podID
   328  	}
   329  
   330  	expose, err := CreateExpose(c.Expose)
   331  	if err != nil {
   332  		return err
   333  	}
   334  
   335  	if len(s.Expose) == 0 {
   336  		s.Expose = expose
   337  	}
   338  
   339  	if sig := c.StopSignal; len(sig) > 0 {
   340  		stopSignal, err := util.ParseSignal(sig)
   341  		if err != nil {
   342  			return err
   343  		}
   344  		s.StopSignal = &stopSignal
   345  	}
   346  
   347  	// ENVIRONMENT VARIABLES
   348  	//
   349  	// Precedence order (higher index wins):
   350  	//  1) containers.conf (EnvHost, EnvHTTP, Env) 2) image data, 3 User EnvHost/EnvHTTP, 4) env-file, 5) env
   351  	// containers.conf handled and image data handled on the server side
   352  	// user specified EnvHost and EnvHTTP handled on Server Side relative to Server
   353  	// env-file and env handled on client side
   354  	var env map[string]string
   355  
   356  	// First transform the os env into a map. We need it for the labels later in
   357  	// any case.
   358  	osEnv, err := envLib.ParseSlice(os.Environ())
   359  	if err != nil {
   360  		return errors.Wrap(err, "error parsing host environment variables")
   361  	}
   362  
   363  	if !s.EnvHost {
   364  		s.EnvHost = c.EnvHost
   365  	}
   366  
   367  	if !s.HTTPProxy {
   368  		s.HTTPProxy = c.HTTPProxy
   369  	}
   370  
   371  	// env-file overrides any previous variables
   372  	for _, f := range c.EnvFile {
   373  		fileEnv, err := envLib.ParseFile(f)
   374  		if err != nil {
   375  			return err
   376  		}
   377  		// File env is overridden by env.
   378  		env = envLib.Join(env, fileEnv)
   379  	}
   380  
   381  	parsedEnv, err := envLib.ParseSlice(c.Env)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	if len(s.Env) == 0 {
   387  		s.Env = envLib.Join(env, parsedEnv)
   388  	}
   389  
   390  	// LABEL VARIABLES
   391  	labels, err := parse.GetAllLabels(c.LabelFile, c.Label)
   392  	if err != nil {
   393  		return errors.Wrapf(err, "unable to process labels")
   394  	}
   395  
   396  	if systemdUnit, exists := osEnv[systemdDefine.EnvVariable]; exists {
   397  		labels[systemdDefine.EnvVariable] = systemdUnit
   398  	}
   399  
   400  	if len(s.Labels) == 0 {
   401  		s.Labels = labels
   402  	}
   403  
   404  	// ANNOTATIONS
   405  	annotations := make(map[string]string)
   406  
   407  	// First, add our default annotations
   408  	annotations[ann.TTY] = "false"
   409  	if c.TTY {
   410  		annotations[ann.TTY] = "true"
   411  	}
   412  
   413  	// Last, add user annotations
   414  	for _, annotation := range c.Annotation {
   415  		splitAnnotation := strings.SplitN(annotation, "=", 2)
   416  		if len(splitAnnotation) < 2 {
   417  			return errors.Errorf("Annotations must be formatted KEY=VALUE")
   418  		}
   419  		annotations[splitAnnotation[0]] = splitAnnotation[1]
   420  	}
   421  	if len(s.Annotations) == 0 {
   422  		s.Annotations = annotations
   423  	}
   424  
   425  	if len(c.StorageOpts) > 0 {
   426  		opts := make(map[string]string, len(c.StorageOpts))
   427  		for _, opt := range c.StorageOpts {
   428  			split := strings.SplitN(opt, "=", 2)
   429  			if len(split) != 2 {
   430  				return errors.Errorf("storage-opt must be formatted KEY=VALUE")
   431  			}
   432  			opts[split[0]] = split[1]
   433  		}
   434  		s.StorageOpts = opts
   435  	}
   436  	if len(s.WorkDir) == 0 {
   437  		s.WorkDir = c.Workdir
   438  	}
   439  	if c.Entrypoint != nil {
   440  		entrypoint := []string{}
   441  		// Check if entrypoint specified is json
   442  		if err := json.Unmarshal([]byte(*c.Entrypoint), &entrypoint); err != nil {
   443  			entrypoint = append(entrypoint, *c.Entrypoint)
   444  		}
   445  		s.Entrypoint = entrypoint
   446  	}
   447  
   448  	// Include the command used to create the container.
   449  
   450  	if len(s.ContainerCreateCommand) == 0 {
   451  		s.ContainerCreateCommand = os.Args
   452  	}
   453  
   454  	if len(inputCommand) > 0 {
   455  		s.Command = inputCommand
   456  	}
   457  
   458  	// SHM Size
   459  	if c.ShmSize != "" {
   460  		var m opts.MemBytes
   461  		if err := m.Set(c.ShmSize); err != nil {
   462  			return errors.Wrapf(err, "unable to translate --shm-size")
   463  		}
   464  		val := m.Value()
   465  		s.ShmSize = &val
   466  	}
   467  
   468  	if c.Net != nil {
   469  		s.Networks = c.Net.Networks
   470  	}
   471  
   472  	if c.Net != nil {
   473  		s.HostAdd = c.Net.AddHosts
   474  		s.UseImageResolvConf = c.Net.UseImageResolvConf
   475  		s.DNSServers = c.Net.DNSServers
   476  		s.DNSSearch = c.Net.DNSSearch
   477  		s.DNSOptions = c.Net.DNSOptions
   478  		s.NetworkOptions = c.Net.NetworkOptions
   479  		s.UseImageHosts = c.Net.NoHosts
   480  	}
   481  	if len(s.HostUsers) == 0 || len(c.HostUsers) != 0 {
   482  		s.HostUsers = c.HostUsers
   483  	}
   484  	if len(c.ImageVolume) != 0 {
   485  		if len(s.ImageVolumeMode) == 0 {
   486  			s.ImageVolumeMode = c.ImageVolume
   487  		}
   488  	}
   489  	if len(s.ImageVolumeMode) == 0 {
   490  		s.ImageVolumeMode = rtc.Engine.ImageVolumeMode
   491  	}
   492  	if s.ImageVolumeMode == "bind" {
   493  		s.ImageVolumeMode = "anonymous"
   494  	}
   495  
   496  	if len(s.Systemd) == 0 || len(c.Systemd) != 0 {
   497  		s.Systemd = strings.ToLower(c.Systemd)
   498  	}
   499  	if len(s.SdNotifyMode) == 0 || len(c.SdNotifyMode) != 0 {
   500  		s.SdNotifyMode = c.SdNotifyMode
   501  	}
   502  	if s.ResourceLimits == nil {
   503  		s.ResourceLimits = &specs.LinuxResources{}
   504  	}
   505  
   506  	if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) {
   507  		s.ResourceLimits.Memory, err = getMemoryLimits(c)
   508  		if err != nil {
   509  			return err
   510  		}
   511  	}
   512  	if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0) {
   513  		s.ResourceLimits.BlockIO, err = getIOLimits(s, c)
   514  		if err != nil {
   515  			return err
   516  		}
   517  	}
   518  	if c.PIDsLimit != nil {
   519  		pids := specs.LinuxPids{
   520  			Limit: *c.PIDsLimit,
   521  		}
   522  
   523  		s.ResourceLimits.Pids = &pids
   524  	}
   525  
   526  	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) {
   527  		s.ResourceLimits.CPU = getCPULimits(c)
   528  	}
   529  
   530  	unifieds := make(map[string]string)
   531  	for _, unified := range c.CgroupConf {
   532  		splitUnified := strings.SplitN(unified, "=", 2)
   533  		if len(splitUnified) < 2 {
   534  			return errors.Errorf("--cgroup-conf must be formatted KEY=VALUE")
   535  		}
   536  		unifieds[splitUnified[0]] = splitUnified[1]
   537  	}
   538  	if len(unifieds) > 0 {
   539  		s.ResourceLimits.Unified = unifieds
   540  	}
   541  
   542  	if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil {
   543  		s.ResourceLimits = nil
   544  	}
   545  
   546  	if s.LogConfiguration == nil {
   547  		s.LogConfiguration = &specgen.LogConfig{}
   548  	}
   549  
   550  	if ld := c.LogDriver; len(ld) > 0 {
   551  		s.LogConfiguration.Driver = ld
   552  	}
   553  	if len(s.CgroupParent) == 0 || len(c.CgroupParent) != 0 {
   554  		s.CgroupParent = c.CgroupParent
   555  	}
   556  	if len(s.CgroupsMode) == 0 {
   557  		s.CgroupsMode = c.CgroupsMode
   558  	}
   559  	if s.CgroupsMode == "" {
   560  		s.CgroupsMode = rtc.Cgroups()
   561  	}
   562  
   563  	if len(s.Groups) == 0 || len(c.GroupAdd) != 0 {
   564  		s.Groups = c.GroupAdd
   565  	}
   566  
   567  	if len(s.Hostname) == 0 || len(c.Hostname) != 0 {
   568  		s.Hostname = c.Hostname
   569  	}
   570  	sysctl := map[string]string{}
   571  	if ctl := c.Sysctl; len(ctl) > 0 {
   572  		sysctl, err = util.ValidateSysctls(ctl)
   573  		if err != nil {
   574  			return err
   575  		}
   576  	}
   577  	if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 {
   578  		s.Sysctl = sysctl
   579  	}
   580  
   581  	if len(s.CapAdd) == 0 || len(c.CapAdd) != 0 {
   582  		s.CapAdd = c.CapAdd
   583  	}
   584  	if len(s.CapDrop) == 0 || len(c.CapDrop) != 0 {
   585  		s.CapDrop = c.CapDrop
   586  	}
   587  	if !s.Privileged {
   588  		s.Privileged = c.Privileged
   589  	}
   590  	if !s.ReadOnlyFilesystem {
   591  		s.ReadOnlyFilesystem = c.ReadOnly
   592  	}
   593  	if len(s.ConmonPidFile) == 0 || len(c.ConmonPIDFile) != 0 {
   594  		s.ConmonPidFile = c.ConmonPIDFile
   595  	}
   596  
   597  	if len(s.DependencyContainers) == 0 || len(c.Requires) != 0 {
   598  		s.DependencyContainers = c.Requires
   599  	}
   600  
   601  	// TODO
   602  	// outside of specgen and oci though
   603  	// defaults to true, check spec/storage
   604  	// s.readonly = c.ReadOnlyTmpFS
   605  	//  TODO convert to map?
   606  	// check if key=value and convert
   607  	sysmap := make(map[string]string)
   608  	for _, ctl := range c.Sysctl {
   609  		splitCtl := strings.SplitN(ctl, "=", 2)
   610  		if len(splitCtl) < 2 {
   611  			return errors.Errorf("invalid sysctl value %q", ctl)
   612  		}
   613  		sysmap[splitCtl[0]] = splitCtl[1]
   614  	}
   615  	if len(s.Sysctl) == 0 || len(c.Sysctl) != 0 {
   616  		s.Sysctl = sysmap
   617  	}
   618  
   619  	if c.CIDFile != "" {
   620  		s.Annotations[define.InspectAnnotationCIDFile] = c.CIDFile
   621  	}
   622  
   623  	for _, opt := range c.SecurityOpt {
   624  		if opt == "no-new-privileges" {
   625  			s.ContainerSecurityConfig.NoNewPrivileges = true
   626  		} else {
   627  			// Docker deprecated the ":" syntax but still supports it,
   628  			// so we need to as well
   629  			var con []string
   630  			if strings.Contains(opt, "=") {
   631  				con = strings.SplitN(opt, "=", 2)
   632  			} else {
   633  				con = strings.SplitN(opt, ":", 2)
   634  			}
   635  			if len(con) != 2 {
   636  				return fmt.Errorf("invalid --security-opt 1: %q", opt)
   637  			}
   638  			switch con[0] {
   639  			case "apparmor":
   640  				s.ContainerSecurityConfig.ApparmorProfile = con[1]
   641  				s.Annotations[define.InspectAnnotationApparmor] = con[1]
   642  			case "label":
   643  				// TODO selinux opts and label opts are the same thing
   644  				s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
   645  				s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
   646  			case "mask":
   647  				s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
   648  			case "proc-opts":
   649  				s.ProcOpts = strings.Split(con[1], ",")
   650  			case "seccomp":
   651  				s.SeccompProfilePath = con[1]
   652  				s.Annotations[define.InspectAnnotationSeccomp] = con[1]
   653  			// this option is for docker compatibility, it is the same as unmask=ALL
   654  			case "systempaths":
   655  				if con[1] == "unconfined" {
   656  					s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, []string{"ALL"}...)
   657  				} else {
   658  					return fmt.Errorf("invalid systempaths option %q, only `unconfined` is supported", con[1])
   659  				}
   660  			case "unmask":
   661  				s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, con[1:]...)
   662  			case "no-new-privileges":
   663  				noNewPrivileges, err := strconv.ParseBool(con[1])
   664  				if err != nil {
   665  					return fmt.Errorf("invalid --security-opt 2: %q", opt)
   666  				}
   667  				s.ContainerSecurityConfig.NoNewPrivileges = noNewPrivileges
   668  			default:
   669  				return fmt.Errorf("invalid --security-opt 2: %q", opt)
   670  			}
   671  		}
   672  	}
   673  
   674  	if len(s.SeccompPolicy) == 0 || len(c.SeccompPolicy) != 0 {
   675  		s.SeccompPolicy = c.SeccompPolicy
   676  	}
   677  
   678  	if len(s.VolumesFrom) == 0 || len(c.VolumesFrom) != 0 {
   679  		s.VolumesFrom = c.VolumesFrom
   680  	}
   681  
   682  	// Only add read-only tmpfs mounts in case that we are read-only and the
   683  	// read-only tmpfs flag has been set.
   684  	mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
   685  	if err != nil {
   686  		return err
   687  	}
   688  	if len(s.Mounts) == 0 || len(c.Mount) != 0 {
   689  		s.Mounts = mounts
   690  	}
   691  	if len(s.Volumes) == 0 || len(c.Volume) != 0 {
   692  		s.Volumes = volumes
   693  	}
   694  	// TODO make sure these work in clone
   695  	if len(s.OverlayVolumes) == 0 {
   696  		s.OverlayVolumes = overlayVolumes
   697  	}
   698  	if len(s.ImageVolumes) == 0 {
   699  		s.ImageVolumes = imageVolumes
   700  	}
   701  
   702  	for _, dev := range c.Devices {
   703  		s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
   704  	}
   705  
   706  	for _, rule := range c.DeviceCgroupRule {
   707  		dev, err := parseLinuxResourcesDeviceAccess(rule)
   708  		if err != nil {
   709  			return err
   710  		}
   711  		s.DeviceCgroupRule = append(s.DeviceCgroupRule, dev)
   712  	}
   713  
   714  	if !s.Init {
   715  		s.Init = c.Init
   716  	}
   717  	if len(s.InitPath) == 0 || len(c.InitPath) != 0 {
   718  		s.InitPath = c.InitPath
   719  	}
   720  	if !s.Stdin {
   721  		s.Stdin = c.Interactive
   722  	}
   723  	// quiet
   724  	// DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
   725  
   726  	// Rlimits/Ulimits
   727  	for _, u := range c.Ulimit {
   728  		if u == "host" {
   729  			s.Rlimits = nil
   730  			break
   731  		}
   732  		ul, err := units.ParseUlimit(u)
   733  		if err != nil {
   734  			return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
   735  		}
   736  		rl := specs.POSIXRlimit{
   737  			Type: ul.Name,
   738  			Hard: uint64(ul.Hard),
   739  			Soft: uint64(ul.Soft),
   740  		}
   741  		s.Rlimits = append(s.Rlimits, rl)
   742  	}
   743  
   744  	logOpts := make(map[string]string)
   745  	for _, o := range c.LogOptions {
   746  		split := strings.SplitN(o, "=", 2)
   747  		if len(split) < 2 {
   748  			return errors.Errorf("invalid log option %q", o)
   749  		}
   750  		switch strings.ToLower(split[0]) {
   751  		case "driver":
   752  			s.LogConfiguration.Driver = split[1]
   753  		case "path":
   754  			s.LogConfiguration.Path = split[1]
   755  		case "max-size":
   756  			logSize, err := units.FromHumanSize(split[1])
   757  			if err != nil {
   758  				return err
   759  			}
   760  			s.LogConfiguration.Size = logSize
   761  		default:
   762  			logOpts[split[0]] = split[1]
   763  		}
   764  	}
   765  	if len(s.LogConfiguration.Options) == 0 || len(c.LogOptions) != 0 {
   766  		s.LogConfiguration.Options = logOpts
   767  	}
   768  	if len(s.Name) == 0 || len(c.Name) != 0 {
   769  		s.Name = c.Name
   770  	}
   771  	if s.PreserveFDs == 0 || c.PreserveFDs != 0 {
   772  		s.PreserveFDs = c.PreserveFDs
   773  	}
   774  
   775  	if s.OOMScoreAdj == nil || c.OOMScoreAdj != nil {
   776  		s.OOMScoreAdj = c.OOMScoreAdj
   777  	}
   778  	if c.Restart != "" {
   779  		splitRestart := strings.Split(c.Restart, ":")
   780  		switch len(splitRestart) {
   781  		case 1:
   782  			// No retries specified
   783  		case 2:
   784  			if strings.ToLower(splitRestart[0]) != "on-failure" {
   785  				return errors.Errorf("restart policy retries can only be specified with on-failure restart policy")
   786  			}
   787  			retries, err := strconv.Atoi(splitRestart[1])
   788  			if err != nil {
   789  				return errors.Wrapf(err, "error parsing restart policy retry count")
   790  			}
   791  			if retries < 0 {
   792  				return errors.Errorf("must specify restart policy retry count as a number greater than 0")
   793  			}
   794  			var retriesUint = uint(retries)
   795  			s.RestartRetries = &retriesUint
   796  		default:
   797  			return errors.Errorf("invalid restart policy: may specify retries at most once")
   798  		}
   799  		s.RestartPolicy = splitRestart[0]
   800  	}
   801  
   802  	if len(s.Secrets) == 0 || len(c.Secrets) != 0 {
   803  		s.Secrets, s.EnvSecrets, err = parseSecrets(c.Secrets)
   804  		if err != nil {
   805  			return err
   806  		}
   807  	}
   808  
   809  	if c.Personality != "" {
   810  		s.Personality = &specs.LinuxPersonality{}
   811  		s.Personality.Domain = specs.LinuxPersonalityDomain(c.Personality)
   812  	}
   813  
   814  	if !s.Remove {
   815  		s.Remove = c.Rm
   816  	}
   817  	if s.StopTimeout == nil || c.StopTimeout != 0 {
   818  		s.StopTimeout = &c.StopTimeout
   819  	}
   820  	if s.Timeout == 0 || c.Timeout != 0 {
   821  		s.Timeout = c.Timeout
   822  	}
   823  	if len(s.Timezone) == 0 || len(c.Timezone) != 0 {
   824  		s.Timezone = c.Timezone
   825  	}
   826  	if len(s.Umask) == 0 || len(c.Umask) != 0 {
   827  		s.Umask = c.Umask
   828  	}
   829  	if len(s.PidFile) == 0 || len(c.PidFile) != 0 {
   830  		s.PidFile = c.PidFile
   831  	}
   832  	if !s.Volatile {
   833  		s.Volatile = c.Rm
   834  	}
   835  	if len(s.UnsetEnv) == 0 || len(c.UnsetEnv) != 0 {
   836  		s.UnsetEnv = c.UnsetEnv
   837  	}
   838  	if !s.UnsetEnvAll {
   839  		s.UnsetEnvAll = c.UnsetEnvAll
   840  	}
   841  	if len(s.ChrootDirs) == 0 || len(c.ChrootDirs) != 0 {
   842  		s.ChrootDirs = c.ChrootDirs
   843  	}
   844  
   845  	// Initcontainers
   846  	if len(s.InitContainerType) == 0 || len(c.InitContainerType) != 0 {
   847  		s.InitContainerType = c.InitContainerType
   848  	}
   849  
   850  	t := true
   851  	if s.Passwd == nil {
   852  		s.Passwd = &t
   853  	}
   854  
   855  	if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 {
   856  		s.PasswdEntry = c.PasswdEntry
   857  	}
   858  
   859  	return nil
   860  }
   861  
   862  func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) {
   863  	cmdArr := []string{}
   864  	isArr := true
   865  	err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling
   866  	if err != nil {
   867  		cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat
   868  		isArr = false
   869  	}
   870  	// Every healthcheck requires a command
   871  	if len(cmdArr) == 0 {
   872  		return nil, errors.New("Must define a healthcheck command for all healthchecks")
   873  	}
   874  
   875  	var concat string
   876  	if cmdArr[0] == "CMD" || cmdArr[0] == "none" { // this is for compat, we are already split properly for most compat cases
   877  		cmdArr = strings.Fields(inCmd)
   878  	} else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, won't contain the keywords
   879  		if isArr && len(cmdArr) > 1 { // an array of consecutive commands
   880  			cmdArr = append([]string{"CMD"}, cmdArr...)
   881  		} else { // one singular command
   882  			if len(cmdArr) == 1 {
   883  				concat = cmdArr[0]
   884  			} else {
   885  				concat = strings.Join(cmdArr[0:], " ")
   886  			}
   887  			cmdArr = append([]string{"CMD-SHELL"}, concat)
   888  		}
   889  	}
   890  
   891  	if cmdArr[0] == "none" { // if specified to remove healtcheck
   892  		cmdArr = []string{"NONE"}
   893  	}
   894  
   895  	// healthcheck is by default an array, so we simply pass the user input
   896  	hc := manifest.Schema2HealthConfig{
   897  		Test: cmdArr,
   898  	}
   899  
   900  	if interval == "disable" {
   901  		interval = "0"
   902  	}
   903  	intervalDuration, err := time.ParseDuration(interval)
   904  	if err != nil {
   905  		return nil, errors.Wrapf(err, "invalid healthcheck-interval")
   906  	}
   907  
   908  	hc.Interval = intervalDuration
   909  
   910  	if retries < 1 {
   911  		return nil, errors.New("healthcheck-retries must be greater than 0")
   912  	}
   913  	hc.Retries = int(retries)
   914  	timeoutDuration, err := time.ParseDuration(timeout)
   915  	if err != nil {
   916  		return nil, errors.Wrapf(err, "invalid healthcheck-timeout")
   917  	}
   918  	if timeoutDuration < time.Duration(1) {
   919  		return nil, errors.New("healthcheck-timeout must be at least 1 second")
   920  	}
   921  	hc.Timeout = timeoutDuration
   922  
   923  	startPeriodDuration, err := time.ParseDuration(startPeriod)
   924  	if err != nil {
   925  		return nil, errors.Wrapf(err, "invalid healthcheck-start-period")
   926  	}
   927  	if startPeriodDuration < time.Duration(0) {
   928  		return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
   929  	}
   930  	hc.StartPeriod = startPeriodDuration
   931  
   932  	return &hc, nil
   933  }
   934  
   935  func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) {
   936  	wd := make(map[string]specs.LinuxWeightDevice)
   937  	for _, val := range weightDevs {
   938  		split := strings.SplitN(val, ":", 2)
   939  		if len(split) != 2 {
   940  			return nil, fmt.Errorf("bad format: %s", val)
   941  		}
   942  		if !strings.HasPrefix(split[0], "/dev/") {
   943  			return nil, fmt.Errorf("bad format for device path: %s", val)
   944  		}
   945  		weight, err := strconv.ParseUint(split[1], 10, 0)
   946  		if err != nil {
   947  			return nil, fmt.Errorf("invalid weight for device: %s", val)
   948  		}
   949  		if weight > 0 && (weight < 10 || weight > 1000) {
   950  			return nil, fmt.Errorf("invalid weight for device: %s", val)
   951  		}
   952  		w := uint16(weight)
   953  		wd[split[0]] = specs.LinuxWeightDevice{
   954  			Weight:     &w,
   955  			LeafWeight: nil,
   956  		}
   957  	}
   958  	return wd, nil
   959  }
   960  
   961  func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
   962  	td := make(map[string]specs.LinuxThrottleDevice)
   963  	for _, val := range bpsDevices {
   964  		split := strings.SplitN(val, ":", 2)
   965  		if len(split) != 2 {
   966  			return nil, fmt.Errorf("bad format: %s", val)
   967  		}
   968  		if !strings.HasPrefix(split[0], "/dev/") {
   969  			return nil, fmt.Errorf("bad format for device path: %s", val)
   970  		}
   971  		rate, err := units.RAMInBytes(split[1])
   972  		if err != nil {
   973  			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", val)
   974  		}
   975  		if rate < 0 {
   976  			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", val)
   977  		}
   978  		td[split[0]] = specs.LinuxThrottleDevice{Rate: uint64(rate)}
   979  	}
   980  	return td, nil
   981  }
   982  
   983  func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrottleDevice, error) {
   984  	td := make(map[string]specs.LinuxThrottleDevice)
   985  	for _, val := range iopsDevices {
   986  		split := strings.SplitN(val, ":", 2)
   987  		if len(split) != 2 {
   988  			return nil, fmt.Errorf("bad format: %s", val)
   989  		}
   990  		if !strings.HasPrefix(split[0], "/dev/") {
   991  			return nil, fmt.Errorf("bad format for device path: %s", val)
   992  		}
   993  		rate, err := strconv.ParseUint(split[1], 10, 64)
   994  		if err != nil {
   995  			return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
   996  		}
   997  		td[split[0]] = specs.LinuxThrottleDevice{Rate: rate}
   998  	}
   999  	return td, nil
  1000  }
  1001  
  1002  func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) {
  1003  	secretParseError := errors.New("parsing secret")
  1004  	var mount []specgen.Secret
  1005  	envs := make(map[string]string)
  1006  	for _, val := range secrets {
  1007  		// mount only tells if user has set an option that can only be used with mount secret type
  1008  		mountOnly := false
  1009  		source := ""
  1010  		secretType := ""
  1011  		target := ""
  1012  		var uid, gid uint32
  1013  		// default mode 444 octal = 292 decimal
  1014  		var mode uint32 = 292
  1015  		split := strings.Split(val, ",")
  1016  
  1017  		// --secret mysecret
  1018  		if len(split) == 1 {
  1019  			mountSecret := specgen.Secret{
  1020  				Source: val,
  1021  				Target: target,
  1022  				UID:    uid,
  1023  				GID:    gid,
  1024  				Mode:   mode,
  1025  			}
  1026  			mount = append(mount, mountSecret)
  1027  			continue
  1028  		}
  1029  		// --secret mysecret,opt=opt
  1030  		if !strings.Contains(split[0], "=") {
  1031  			source = split[0]
  1032  			split = split[1:]
  1033  		}
  1034  
  1035  		for _, val := range split {
  1036  			kv := strings.SplitN(val, "=", 2)
  1037  			if len(kv) < 2 {
  1038  				return nil, nil, errors.Wrapf(secretParseError, "option %s must be in form option=value", val)
  1039  			}
  1040  			switch kv[0] {
  1041  			case "source":
  1042  				source = kv[1]
  1043  			case "type":
  1044  				if secretType != "" {
  1045  					return nil, nil, errors.Wrap(secretParseError, "cannot set more than one secret type")
  1046  				}
  1047  				if kv[1] != "mount" && kv[1] != "env" {
  1048  					return nil, nil, errors.Wrapf(secretParseError, "type %s is invalid", kv[1])
  1049  				}
  1050  				secretType = kv[1]
  1051  			case "target":
  1052  				target = kv[1]
  1053  			case "mode":
  1054  				mountOnly = true
  1055  				mode64, err := strconv.ParseUint(kv[1], 8, 32)
  1056  				if err != nil {
  1057  					return nil, nil, errors.Wrapf(secretParseError, "mode %s invalid", kv[1])
  1058  				}
  1059  				mode = uint32(mode64)
  1060  			case "uid", "UID":
  1061  				mountOnly = true
  1062  				uid64, err := strconv.ParseUint(kv[1], 10, 32)
  1063  				if err != nil {
  1064  					return nil, nil, errors.Wrapf(secretParseError, "UID %s invalid", kv[1])
  1065  				}
  1066  				uid = uint32(uid64)
  1067  			case "gid", "GID":
  1068  				mountOnly = true
  1069  				gid64, err := strconv.ParseUint(kv[1], 10, 32)
  1070  				if err != nil {
  1071  					return nil, nil, errors.Wrapf(secretParseError, "GID %s invalid", kv[1])
  1072  				}
  1073  				gid = uint32(gid64)
  1074  
  1075  			default:
  1076  				return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val)
  1077  			}
  1078  		}
  1079  
  1080  		if secretType == "" {
  1081  			secretType = "mount"
  1082  		}
  1083  		if source == "" {
  1084  			return nil, nil, errors.Wrapf(secretParseError, "no source found %s", val)
  1085  		}
  1086  		if secretType == "mount" {
  1087  			mountSecret := specgen.Secret{
  1088  				Source: source,
  1089  				Target: target,
  1090  				UID:    uid,
  1091  				GID:    gid,
  1092  				Mode:   mode,
  1093  			}
  1094  			mount = append(mount, mountSecret)
  1095  		}
  1096  		if secretType == "env" {
  1097  			if mountOnly {
  1098  				return nil, nil, errors.Wrap(secretParseError, "UID, GID, Mode options cannot be set with secret type env")
  1099  			}
  1100  			if target == "" {
  1101  				target = source
  1102  			}
  1103  			envs[target] = source
  1104  		}
  1105  	}
  1106  	return mount, envs, nil
  1107  }
  1108  
  1109  var cgroupDeviceType = map[string]bool{
  1110  	"a": true, // all
  1111  	"b": true, // block device
  1112  	"c": true, // character device
  1113  }
  1114  
  1115  var cgroupDeviceAccess = map[string]bool{
  1116  	"r": true, // read
  1117  	"w": true, // write
  1118  	"m": true, // mknod
  1119  }
  1120  
  1121  // parseLinuxResourcesDeviceAccess parses the raw string passed with the --device-access-add flag
  1122  func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, error) {
  1123  	var devType, access string
  1124  	var major, minor *int64
  1125  
  1126  	value := strings.Split(device, " ")
  1127  	if len(value) != 3 {
  1128  		return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device cgroup rule requires type, major:Minor, and access rules: %q", device)
  1129  	}
  1130  
  1131  	devType = value[0]
  1132  	if !cgroupDeviceType[devType] {
  1133  		return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device type in device-access-add: %s", devType)
  1134  	}
  1135  
  1136  	number := strings.SplitN(value[1], ":", 2)
  1137  	i, err := strconv.ParseInt(number[0], 10, 64)
  1138  	if err != nil {
  1139  		return specs.LinuxDeviceCgroup{}, err
  1140  	}
  1141  	major = &i
  1142  	if len(number) == 2 && number[1] != "*" {
  1143  		i, err := strconv.ParseInt(number[1], 10, 64)
  1144  		if err != nil {
  1145  			return specs.LinuxDeviceCgroup{}, err
  1146  		}
  1147  		minor = &i
  1148  	}
  1149  	access = value[2]
  1150  	for _, c := range strings.Split(access, "") {
  1151  		if !cgroupDeviceAccess[c] {
  1152  			return specs.LinuxDeviceCgroup{}, fmt.Errorf("invalid device access in device-access-add: %s", c)
  1153  		}
  1154  	}
  1155  	return specs.LinuxDeviceCgroup{
  1156  		Allow:  true,
  1157  		Type:   devType,
  1158  		Major:  major,
  1159  		Minor:  minor,
  1160  		Access: access,
  1161  	}, nil
  1162  }