mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00

Unify handling for the --volume, --mount, --volumes-from, --tmpfs and --init flags into a single file and set of functions. This will greatly improve readability and maintainability. Further, properly handle superceding and conflicting mounts. Our current patchwork has serious issues when mounts conflict, or when a mount from --volumes-from or an image volume should be overwritten by a user volume or named volume. Signed-off-by: Matthew Heon <matthew.heon@pm.me>
420 lines
14 KiB
Go
420 lines
14 KiB
Go
package createconfig
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/containers/image/manifest"
|
|
"github.com/containers/libpod/libpod"
|
|
"github.com/containers/libpod/pkg/namespaces"
|
|
"github.com/containers/storage"
|
|
"github.com/cri-o/ocicni/pkg/ocicni"
|
|
"github.com/docker/go-connections/nat"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/opencontainers/runtime-tools/generate"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Type constants
|
|
const (
|
|
bps = iota
|
|
iops
|
|
)
|
|
|
|
// CreateResourceConfig represents resource elements in CreateConfig
|
|
// structures
|
|
type CreateResourceConfig struct {
|
|
BlkioWeight uint16 // blkio-weight
|
|
BlkioWeightDevice []string // blkio-weight-device
|
|
CPUPeriod uint64 // cpu-period
|
|
CPUQuota int64 // cpu-quota
|
|
CPURtPeriod uint64 // cpu-rt-period
|
|
CPURtRuntime int64 // cpu-rt-runtime
|
|
CPUShares uint64 // cpu-shares
|
|
CPUs float64 // cpus
|
|
CPUsetCPUs string
|
|
CPUsetMems string // cpuset-mems
|
|
DeviceReadBps []string // device-read-bps
|
|
DeviceReadIOps []string // device-read-iops
|
|
DeviceWriteBps []string // device-write-bps
|
|
DeviceWriteIOps []string // device-write-iops
|
|
DisableOomKiller bool // oom-kill-disable
|
|
KernelMemory int64 // kernel-memory
|
|
Memory int64 //memory
|
|
MemoryReservation int64 // memory-reservation
|
|
MemorySwap int64 //memory-swap
|
|
MemorySwappiness int // memory-swappiness
|
|
OomScoreAdj int //oom-score-adj
|
|
PidsLimit int64 // pids-limit
|
|
ShmSize int64
|
|
Ulimit []string //ulimit
|
|
}
|
|
|
|
// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI
|
|
type CreateConfig struct {
|
|
Annotations map[string]string
|
|
Args []string
|
|
CapAdd []string // cap-add
|
|
CapDrop []string // cap-drop
|
|
CidFile string
|
|
ConmonPidFile string
|
|
CgroupParent string // cgroup-parent
|
|
Command []string
|
|
Detach bool // detach
|
|
Devices []string // device
|
|
DNSOpt []string //dns-opt
|
|
DNSSearch []string //dns-search
|
|
DNSServers []string //dns
|
|
Entrypoint []string //entrypoint
|
|
Env map[string]string //env
|
|
ExposedPorts map[nat.Port]struct{}
|
|
GroupAdd []string // group-add
|
|
HealthCheck *manifest.Schema2HealthConfig
|
|
NoHosts bool
|
|
HostAdd []string //add-host
|
|
Hostname string //hostname
|
|
HTTPProxy bool
|
|
Init bool // init
|
|
InitPath string //init-path
|
|
Image string
|
|
ImageID string
|
|
BuiltinImgVolumes map[string]struct{} // volumes defined in the image config
|
|
IDMappings *storage.IDMappingOptions
|
|
ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore
|
|
Interactive bool //interactive
|
|
IpcMode namespaces.IpcMode //ipc
|
|
IP6Address string //ipv6
|
|
IPAddress string //ip
|
|
Labels map[string]string //label
|
|
LinkLocalIP []string // link-local-ip
|
|
LogDriver string // log-driver
|
|
LogDriverOpt []string // log-opt
|
|
MacAddress string //mac-address
|
|
Name string //name
|
|
NetMode namespaces.NetworkMode //net
|
|
Network string //network
|
|
NetworkAlias []string //network-alias
|
|
PidMode namespaces.PidMode //pid
|
|
Pod string //pod
|
|
PortBindings nat.PortMap
|
|
Privileged bool //privileged
|
|
Publish []string //publish
|
|
PublishAll bool //publish-all
|
|
Quiet bool //quiet
|
|
ReadOnlyRootfs bool //read-only
|
|
ReadOnlyTmpfs bool //read-only-tmpfs
|
|
Resources CreateResourceConfig
|
|
Rm bool //rm
|
|
StopSignal syscall.Signal // stop-signal
|
|
StopTimeout uint // stop-timeout
|
|
Sysctl map[string]string //sysctl
|
|
Systemd bool
|
|
Tmpfs []string // tmpfs
|
|
Tty bool //tty
|
|
UsernsMode namespaces.UsernsMode //userns
|
|
User string //user
|
|
UtsMode namespaces.UTSMode //uts
|
|
Mounts []spec.Mount
|
|
MountsFlag []string // mounts
|
|
NamedVolumes []*libpod.ContainerNamedVolume
|
|
Volumes []string //volume
|
|
VolumesFrom []string
|
|
WorkDir string //workdir
|
|
LabelOpts []string //SecurityOpts
|
|
NoNewPrivs bool //SecurityOpts
|
|
ApparmorProfile string //SecurityOpts
|
|
SeccompProfilePath string //SecurityOpts
|
|
SecurityOpts []string
|
|
Rootfs string
|
|
Syslog bool // Whether to enable syslog on exit commands
|
|
}
|
|
|
|
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
|
|
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
|
|
|
// CreateBlockIO returns a LinuxBlockIO struct from a CreateConfig
|
|
func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
|
|
return c.createBlockIO()
|
|
}
|
|
|
|
func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, error) {
|
|
config, err := runtime.GetConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd, _ := os.Executable()
|
|
command := []string{cmd,
|
|
"--root", config.StorageConfig.GraphRoot,
|
|
"--runroot", config.StorageConfig.RunRoot,
|
|
"--log-level", logrus.GetLevel().String(),
|
|
"--cgroup-manager", config.CgroupManager,
|
|
"--tmpdir", config.TmpDir,
|
|
}
|
|
if config.OCIRuntime != "" {
|
|
command = append(command, []string{"--runtime", config.OCIRuntime}...)
|
|
}
|
|
if config.StorageConfig.GraphDriverName != "" {
|
|
command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...)
|
|
}
|
|
if c.Syslog {
|
|
command = append(command, "--syslog", "true")
|
|
}
|
|
command = append(command, []string{"container", "cleanup"}...)
|
|
|
|
if c.Rm {
|
|
command = append(command, "--rm")
|
|
}
|
|
|
|
return command, nil
|
|
}
|
|
|
|
// GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions
|
|
func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, mounts []spec.Mount, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) {
|
|
var options []libpod.CtrCreateOption
|
|
var portBindings []ocicni.PortMapping
|
|
var err error
|
|
|
|
if c.Interactive {
|
|
options = append(options, libpod.WithStdin())
|
|
}
|
|
if c.Systemd && (strings.HasSuffix(c.Command[0], "init") ||
|
|
strings.HasSuffix(c.Command[0], "systemd")) {
|
|
options = append(options, libpod.WithSystemd())
|
|
}
|
|
if c.Name != "" {
|
|
logrus.Debugf("setting container name %s", c.Name)
|
|
options = append(options, libpod.WithName(c.Name))
|
|
}
|
|
if c.Pod != "" {
|
|
logrus.Debugf("adding container to pod %s", c.Pod)
|
|
options = append(options, runtime.WithPod(pod))
|
|
}
|
|
if len(c.PortBindings) > 0 {
|
|
portBindings, err = c.CreatePortBindings()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to create port bindings")
|
|
}
|
|
}
|
|
|
|
if len(mounts) != 0 || len(namedVolumes) != 0 {
|
|
destinations := []string{}
|
|
|
|
// Take all mount and named volume destinations.
|
|
for _, mount := range mounts {
|
|
destinations = append(destinations, mount.Destination)
|
|
}
|
|
for _, volume := range namedVolumes {
|
|
destinations = append(destinations, volume.Dest)
|
|
}
|
|
|
|
options = append(options, libpod.WithUserVolumes(destinations))
|
|
}
|
|
|
|
if len(namedVolumes) != 0 {
|
|
options = append(options, libpod.WithNamedVolumes(namedVolumes))
|
|
}
|
|
|
|
if len(c.Command) != 0 {
|
|
options = append(options, libpod.WithCommand(c.Command))
|
|
}
|
|
|
|
// Add entrypoint unconditionally
|
|
// If it's empty it's because it was explicitly set to "" or the image
|
|
// does not have one
|
|
options = append(options, libpod.WithEntrypoint(c.Entrypoint))
|
|
|
|
networks := make([]string, 0)
|
|
userNetworks := c.NetMode.UserDefined()
|
|
if IsPod(userNetworks) {
|
|
userNetworks = ""
|
|
}
|
|
if userNetworks != "" {
|
|
for _, netName := range strings.Split(userNetworks, ",") {
|
|
if netName == "" {
|
|
return nil, errors.Wrapf(err, "container networks %q invalid", networks)
|
|
}
|
|
networks = append(networks, netName)
|
|
}
|
|
}
|
|
|
|
if c.NetMode.IsNS() {
|
|
ns := c.NetMode.NS()
|
|
if ns == "" {
|
|
return nil, errors.Errorf("invalid empty user-defined network namespace")
|
|
}
|
|
_, err := os.Stat(ns)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if c.NetMode.IsContainer() {
|
|
connectedCtr, err := runtime.LookupContainer(c.NetMode.Container())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "container %q not found", c.NetMode.Container())
|
|
}
|
|
options = append(options, libpod.WithNetNSFrom(connectedCtr))
|
|
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
|
|
postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
|
|
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
|
|
}
|
|
|
|
if c.PidMode.IsContainer() {
|
|
connectedCtr, err := runtime.LookupContainer(c.PidMode.Container())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container())
|
|
}
|
|
|
|
options = append(options, libpod.WithPIDNSFrom(connectedCtr))
|
|
}
|
|
|
|
if c.IpcMode.IsContainer() {
|
|
connectedCtr, err := runtime.LookupContainer(c.IpcMode.Container())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container())
|
|
}
|
|
|
|
options = append(options, libpod.WithIPCNSFrom(connectedCtr))
|
|
}
|
|
|
|
if IsPod(string(c.UtsMode)) {
|
|
options = append(options, libpod.WithUTSNSFromPod(pod))
|
|
}
|
|
if c.UtsMode.IsContainer() {
|
|
connectedCtr, err := runtime.LookupContainer(c.UtsMode.Container())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "container %q not found", c.UtsMode.Container())
|
|
}
|
|
|
|
options = append(options, libpod.WithUTSNSFrom(connectedCtr))
|
|
}
|
|
|
|
// TODO: MNT, USER, CGROUP
|
|
options = append(options, libpod.WithStopSignal(c.StopSignal))
|
|
options = append(options, libpod.WithStopTimeout(c.StopTimeout))
|
|
if len(c.DNSSearch) > 0 {
|
|
options = append(options, libpod.WithDNSSearch(c.DNSSearch))
|
|
}
|
|
if len(c.DNSServers) > 0 {
|
|
if len(c.DNSServers) == 1 && strings.ToLower(c.DNSServers[0]) == "none" {
|
|
options = append(options, libpod.WithUseImageResolvConf())
|
|
} else {
|
|
options = append(options, libpod.WithDNS(c.DNSServers))
|
|
}
|
|
}
|
|
if len(c.DNSOpt) > 0 {
|
|
options = append(options, libpod.WithDNSOption(c.DNSOpt))
|
|
}
|
|
if c.NoHosts {
|
|
options = append(options, libpod.WithUseImageHosts())
|
|
}
|
|
if len(c.HostAdd) > 0 && !c.NoHosts {
|
|
options = append(options, libpod.WithHosts(c.HostAdd))
|
|
}
|
|
logPath := getLoggingPath(c.LogDriverOpt)
|
|
if logPath != "" {
|
|
options = append(options, libpod.WithLogPath(logPath))
|
|
}
|
|
if c.IPAddress != "" {
|
|
ip := net.ParseIP(c.IPAddress)
|
|
if ip == nil {
|
|
return nil, errors.Wrapf(libpod.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress)
|
|
} else if ip.To4() == nil {
|
|
return nil, errors.Wrapf(libpod.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress)
|
|
}
|
|
options = append(options, libpod.WithStaticIP(ip))
|
|
}
|
|
|
|
options = append(options, libpod.WithPrivileged(c.Privileged))
|
|
|
|
useImageVolumes := c.ImageVolumeType == TypeBind
|
|
// Gather up the options for NewContainer which consist of With... funcs
|
|
options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, useImageVolumes))
|
|
options = append(options, libpod.WithSecLabels(c.LabelOpts))
|
|
options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile))
|
|
options = append(options, libpod.WithLabels(c.Labels))
|
|
options = append(options, libpod.WithUser(c.User))
|
|
if c.IpcMode.IsHost() {
|
|
options = append(options, libpod.WithShmDir("/dev/shm"))
|
|
|
|
} else if c.IpcMode.IsContainer() {
|
|
ctr, err := runtime.LookupContainer(c.IpcMode.Container())
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container())
|
|
}
|
|
options = append(options, libpod.WithShmDir(ctr.ShmDir()))
|
|
}
|
|
options = append(options, libpod.WithShmSize(c.Resources.ShmSize))
|
|
options = append(options, libpod.WithGroups(c.GroupAdd))
|
|
options = append(options, libpod.WithIDMappings(*c.IDMappings))
|
|
if c.Rootfs != "" {
|
|
options = append(options, libpod.WithRootFS(c.Rootfs))
|
|
}
|
|
// Default used if not overridden on command line
|
|
|
|
if c.CgroupParent != "" {
|
|
options = append(options, libpod.WithCgroupParent(c.CgroupParent))
|
|
}
|
|
|
|
// Always use a cleanup process to clean up Podman after termination
|
|
exitCmd, err := c.createExitCommand(runtime)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
options = append(options, libpod.WithExitCommand(exitCmd))
|
|
|
|
if c.HealthCheck != nil {
|
|
options = append(options, libpod.WithHealthCheck(c.HealthCheck))
|
|
logrus.Debugf("New container has a health check")
|
|
}
|
|
return options, nil
|
|
}
|
|
|
|
// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
|
|
func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) {
|
|
return NatToOCIPortBindings(c.PortBindings)
|
|
}
|
|
|
|
// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice
|
|
func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) {
|
|
var portBindings []ocicni.PortMapping
|
|
for containerPb, hostPb := range ports {
|
|
var pm ocicni.PortMapping
|
|
pm.ContainerPort = int32(containerPb.Int())
|
|
for _, i := range hostPb {
|
|
var hostPort int
|
|
var err error
|
|
pm.HostIP = i.HostIP
|
|
if i.HostPort == "" {
|
|
hostPort = containerPb.Int()
|
|
} else {
|
|
hostPort, err = strconv.Atoi(i.HostPort)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to convert host port to integer")
|
|
}
|
|
}
|
|
|
|
pm.HostPort = int32(hostPort)
|
|
pm.Protocol = containerPb.Proto()
|
|
portBindings = append(portBindings, pm)
|
|
}
|
|
}
|
|
return portBindings, nil
|
|
}
|
|
|
|
// AddPrivilegedDevices iterates through host devices and adds all
|
|
// host devices to the spec
|
|
func (c *CreateConfig) AddPrivilegedDevices(g *generate.Generator) error {
|
|
return c.addPrivilegedDevices(g)
|
|
}
|
|
|
|
func getStatFromPath(path string) (unix.Stat_t, error) {
|
|
s := unix.Stat_t{}
|
|
err := unix.Stat(path, &s)
|
|
return s, err
|
|
}
|