github.com/containers/podman/v4@v4.9.4/libpod/container_validate.go (about)

     1  //go:build !remote
     2  // +build !remote
     3  
     4  package libpod
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/containers/image/v5/docker"
    10  	"github.com/containers/image/v5/pkg/shortnames"
    11  	"github.com/containers/image/v5/transports/alltransports"
    12  	"github.com/containers/podman/v4/libpod/define"
    13  	spec "github.com/opencontainers/runtime-spec/specs-go"
    14  )
    15  
    16  // Validate that the configuration of a container is valid.
    17  func (c *Container) validate() error {
    18  	imageIDSet := c.config.RootfsImageID != ""
    19  	imageNameSet := c.config.RootfsImageName != ""
    20  	rootfsSet := c.config.Rootfs != ""
    21  
    22  	// If one of RootfsImageIDor RootfsImageName are set, both must be set.
    23  	if (imageIDSet || imageNameSet) && !(imageIDSet && imageNameSet) {
    24  		return fmt.Errorf("both RootfsImageName and RootfsImageID must be set if either is set: %w", define.ErrInvalidArg)
    25  	}
    26  
    27  	// Cannot set RootfsImageID and Rootfs at the same time
    28  	if imageIDSet && rootfsSet {
    29  		return fmt.Errorf("cannot set both an image ID and rootfs for a container: %w", define.ErrInvalidArg)
    30  	}
    31  
    32  	// Must set at least one of RootfsImageID or Rootfs
    33  	if !(imageIDSet || rootfsSet) {
    34  		return fmt.Errorf("must set root filesystem source to either image or rootfs: %w", define.ErrInvalidArg)
    35  	}
    36  
    37  	// A container cannot be marked as an infra and service container at
    38  	// the same time.
    39  	if c.IsInfra() && c.IsService() {
    40  		return fmt.Errorf("cannot be infra and service container at the same time: %w", define.ErrInvalidArg)
    41  	}
    42  
    43  	// Cannot make a network namespace if we are joining another container's
    44  	// network namespace
    45  	if c.config.CreateNetNS && c.config.NetNsCtr != "" {
    46  		return fmt.Errorf("cannot both create a network namespace and join another container's network namespace: %w", define.ErrInvalidArg)
    47  	}
    48  
    49  	if c.config.CgroupsMode == cgroupSplit && c.config.CgroupParent != "" {
    50  		return fmt.Errorf("cannot specify --cgroup-mode=split with a cgroup-parent: %w", define.ErrInvalidArg)
    51  	}
    52  
    53  	// Not creating cgroups has a number of requirements, mostly related to
    54  	// the PID namespace.
    55  	if c.config.NoCgroups || c.config.CgroupsMode == "disabled" {
    56  		if c.config.PIDNsCtr != "" {
    57  			return fmt.Errorf("cannot join another container's PID namespace if not creating cgroups: %w", define.ErrInvalidArg)
    58  		}
    59  
    60  		if c.config.CgroupParent != "" {
    61  			return fmt.Errorf("cannot set cgroup parent if not creating cgroups: %w", define.ErrInvalidArg)
    62  		}
    63  
    64  		// Ensure we have a PID namespace
    65  		if c.config.Spec.Linux == nil {
    66  			return fmt.Errorf("must provide Linux namespace configuration in OCI spec when using NoCgroups: %w", define.ErrInvalidArg)
    67  		}
    68  		foundPid := false
    69  		for _, ns := range c.config.Spec.Linux.Namespaces {
    70  			if ns.Type == spec.PIDNamespace {
    71  				foundPid = true
    72  				if ns.Path != "" {
    73  					return fmt.Errorf("containers not creating Cgroups must create a private PID namespace - cannot use another: %w", define.ErrInvalidArg)
    74  				}
    75  				break
    76  			}
    77  		}
    78  		if !foundPid {
    79  			return fmt.Errorf("containers not creating Cgroups must create a private PID namespace: %w", define.ErrInvalidArg)
    80  		}
    81  	}
    82  
    83  	// Can only set static IP or MAC is creating a network namespace.
    84  	if !c.config.CreateNetNS && (c.config.StaticIP != nil || c.config.StaticMAC != nil) {
    85  		return fmt.Errorf("cannot set static IP or MAC address if not creating a network namespace: %w", define.ErrInvalidArg)
    86  	}
    87  
    88  	// Cannot set static IP or MAC if joining >1 network.
    89  	if len(c.config.Networks) > 1 && (c.config.StaticIP != nil || c.config.StaticMAC != nil) {
    90  		return fmt.Errorf("cannot set static IP or MAC address if joining more than one network: %w", define.ErrInvalidArg)
    91  	}
    92  
    93  	// Using image resolv.conf conflicts with various DNS settings.
    94  	if c.config.UseImageResolvConf &&
    95  		(len(c.config.DNSSearch) > 0 || len(c.config.DNSServer) > 0 ||
    96  			len(c.config.DNSOption) > 0) {
    97  		return fmt.Errorf("cannot configure DNS options if using image's resolv.conf: %w", define.ErrInvalidArg)
    98  	}
    99  
   100  	if c.config.UseImageHosts && len(c.config.HostAdd) > 0 {
   101  		return fmt.Errorf("cannot add to /etc/hosts if using image's /etc/hosts: %w", define.ErrInvalidArg)
   102  	}
   103  
   104  	// Check named volume, overlay volume and image volume destination conflist
   105  	destinations := make(map[string]bool)
   106  	for _, vol := range c.config.NamedVolumes {
   107  		// Don't check if they already exist.
   108  		// If they don't we will automatically create them.
   109  		if _, ok := destinations[vol.Dest]; ok {
   110  			return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg)
   111  		}
   112  		destinations[vol.Dest] = true
   113  	}
   114  	for _, vol := range c.config.OverlayVolumes {
   115  		// Don't check if they already exist.
   116  		// If they don't we will automatically create them.
   117  		if _, ok := destinations[vol.Dest]; ok {
   118  			return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg)
   119  		}
   120  		destinations[vol.Dest] = true
   121  	}
   122  	for _, vol := range c.config.ImageVolumes {
   123  		// Don't check if they already exist.
   124  		// If they don't we will automatically create them.
   125  		if _, ok := destinations[vol.Dest]; ok {
   126  			return fmt.Errorf("two volumes found with destination %s: %w", vol.Dest, define.ErrInvalidArg)
   127  		}
   128  		destinations[vol.Dest] = true
   129  	}
   130  
   131  	// If User in the OCI spec is set, require that c.config.User is set for
   132  	// security reasons (a lot of our code relies on c.config.User).
   133  	if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) {
   134  		return fmt.Errorf("please set User explicitly via WithUser() instead of in OCI spec directly: %w", define.ErrInvalidArg)
   135  	}
   136  
   137  	// Init-ctrs must be used inside a Pod.  Check if an init container type is
   138  	// passed and if no pod is passed
   139  	if len(c.config.InitContainerType) > 0 && len(c.config.Pod) < 1 {
   140  		return fmt.Errorf("init containers must be created in a pod: %w", define.ErrInvalidArg)
   141  	}
   142  
   143  	if c.config.SdNotifyMode == define.SdNotifyModeIgnore && len(c.config.SdNotifySocket) > 0 {
   144  		return fmt.Errorf("cannot set sd-notify socket %q with sd-notify mode %q", c.config.SdNotifySocket, c.config.SdNotifyMode)
   145  	}
   146  
   147  	if c.config.HealthCheckOnFailureAction != define.HealthCheckOnFailureActionNone && c.config.HealthCheckConfig == nil {
   148  		return fmt.Errorf("cannot set on-failure action to %s without a health check", c.config.HealthCheckOnFailureAction.String())
   149  	}
   150  
   151  	if value, exists := c.config.Labels[define.AutoUpdateLabel]; exists {
   152  		// TODO: we cannot reference pkg/autoupdate here due to
   153  		// circular dependencies.  It's worth considering moving the
   154  		// auto-update logic into the libpod package.
   155  		if value == "registry" || value == "image" {
   156  			if err := validateAutoUpdateImageReference(c.config.RawImageName); err != nil {
   157  				return err
   158  			}
   159  		}
   160  	}
   161  
   162  	// Cannot set startup HC without a healthcheck
   163  	if c.config.HealthCheckConfig == nil && c.config.StartupHealthCheckConfig != nil {
   164  		return fmt.Errorf("cannot set a startup healthcheck when there is no regular healthcheck: %w", define.ErrInvalidArg)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // validateAutoUpdateImageReference checks if the specified imageName is a
   171  // fully-qualified image reference to the docker transport. Such a reference
   172  // includes a domain, name and tag (e.g., quay.io/podman/stable:latest).  The
   173  // reference may also be prefixed with "docker://" explicitly indicating that
   174  // it's a reference to the docker transport.
   175  func validateAutoUpdateImageReference(imageName string) error {
   176  	// Make sure the input image is a docker.
   177  	imageRef, err := alltransports.ParseImageName(imageName)
   178  	if err == nil && imageRef.Transport().Name() != docker.Transport.Name() {
   179  		return fmt.Errorf("auto updates require the docker image transport but image is of transport %q", imageRef.Transport().Name())
   180  	} else if err != nil {
   181  		if shortnames.IsShortName(imageName) {
   182  			return fmt.Errorf("short name: auto updates require fully-qualified image reference: %q", imageName)
   183  		}
   184  	}
   185  	return nil
   186  }