mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-10-10 16:07:38 +00:00
simplify the rootless implementation to use a single user namespace for all the running containers. This makes the rootless implementation behave more like root Podman, where each container is created in the host environment. There are multiple advantages to it: 1) much simpler implementation as there is only one namespace to join. 2) we can join namespaces owned by different containers. 3) commands like ps won't be limited to what container they can access as previously we either had access to the storage from a new namespace or access to /proc when running from the host. 4) rootless varlink works. 5) there are only two ways to enter in a namespace, either by creating a new one if no containers are running or joining the existing one from any container. Containers created by older Podman versions must be restarted. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
845 lines
25 KiB
Go
845 lines
25 KiB
Go
package shared
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containers/image/manifest"
|
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
|
"github.com/containers/libpod/cmd/podman/shared/parse"
|
|
"github.com/containers/libpod/libpod"
|
|
"github.com/containers/libpod/libpod/image"
|
|
ann "github.com/containers/libpod/pkg/annotations"
|
|
"github.com/containers/libpod/pkg/inspect"
|
|
ns "github.com/containers/libpod/pkg/namespaces"
|
|
"github.com/containers/libpod/pkg/rootless"
|
|
cc "github.com/containers/libpod/pkg/spec"
|
|
"github.com/containers/libpod/pkg/util"
|
|
"github.com/docker/docker/pkg/signal"
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/docker/go-units"
|
|
"github.com/google/shlex"
|
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/opencontainers/selinux/go-selinux/label"
|
|
"github.com/opentracing/opentracing-go"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// getContext returns a non-nil, empty context
|
|
func getContext() context.Context {
|
|
return context.TODO()
|
|
}
|
|
|
|
func CreateContainer(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
|
|
var (
|
|
healthCheck *manifest.Schema2HealthConfig
|
|
err error
|
|
cidFile *os.File
|
|
)
|
|
if c.Bool("trace") {
|
|
span, _ := opentracing.StartSpanFromContext(ctx, "createContainer")
|
|
defer span.Finish()
|
|
}
|
|
|
|
rtc, err := runtime.GetConfig()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
rootfs := ""
|
|
if c.Bool("rootfs") {
|
|
rootfs = c.InputArgs[0]
|
|
}
|
|
|
|
if c.IsSet("cidfile") && os.Geteuid() == 0 {
|
|
cidFile, err = libpod.OpenExclusiveFile(c.String("cidfile"))
|
|
if err != nil && os.IsExist(err) {
|
|
return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile"))
|
|
}
|
|
if err != nil {
|
|
return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile"))
|
|
}
|
|
defer cidFile.Close()
|
|
defer cidFile.Sync()
|
|
}
|
|
|
|
imageName := ""
|
|
var data *inspect.ImageData = nil
|
|
|
|
// Set the storage if we are running as euid == 0 and there is no rootfs specified
|
|
if rootfs == "" && os.Geteuid() == 0 {
|
|
var writer io.Writer
|
|
if !c.Bool("quiet") {
|
|
writer = os.Stderr
|
|
}
|
|
|
|
newImage, err := runtime.ImageRuntime().New(ctx, c.InputArgs[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
data, err = newImage.Inspect(ctx)
|
|
names := newImage.Names()
|
|
if len(names) > 0 {
|
|
imageName = names[0]
|
|
} else {
|
|
imageName = newImage.ID()
|
|
}
|
|
|
|
var healthCheckCommandInput string
|
|
// if the user disabled the healthcheck with "none", we skip adding it
|
|
healthCheckCommandInput = c.String("healthcheck-command")
|
|
|
|
// the user didnt disable the healthcheck but did pass in a healthcheck command
|
|
// now we need to make a healthcheck from the commandline input
|
|
if healthCheckCommandInput != "none" {
|
|
if len(healthCheckCommandInput) > 0 {
|
|
healthCheck, err = makeHealthCheckFromCli(c)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "unable to create healthcheck")
|
|
}
|
|
} else {
|
|
// the user did not disable the health check and did not pass in a healthcheck
|
|
// command as input. so now we add healthcheck if it exists AND is correct mediatype
|
|
_, mediaType, err := newImage.Manifest(ctx)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID())
|
|
}
|
|
if mediaType == manifest.DockerV2Schema2MediaType {
|
|
healthCheck, err = newImage.GetHealthCheck(ctx)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, data)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Because parseCreateOpts does derive anything from the image, we add health check
|
|
// at this point. The rest is done by WithOptions.
|
|
createConfig.HealthCheck = healthCheck
|
|
|
|
ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if cidFile != nil {
|
|
_, err = cidFile.WriteString(ctr.ID())
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
|
|
}
|
|
|
|
logrus.Debugf("New container created %q", ctr.ID())
|
|
return ctr, createConfig, nil
|
|
}
|
|
|
|
func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error {
|
|
var (
|
|
labelOpts []string
|
|
)
|
|
|
|
if config.PidMode.IsHost() {
|
|
labelOpts = append(labelOpts, label.DisableSecOpt()...)
|
|
} else if config.PidMode.IsContainer() {
|
|
ctr, err := config.Runtime.LookupContainer(config.PidMode.Container())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "container %q not found", config.PidMode.Container())
|
|
}
|
|
secopts, err := label.DupSecOpt(ctr.ProcessLabel())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
|
|
}
|
|
labelOpts = append(labelOpts, secopts...)
|
|
}
|
|
|
|
if config.IpcMode.IsHost() {
|
|
labelOpts = append(labelOpts, label.DisableSecOpt()...)
|
|
} else if config.IpcMode.IsContainer() {
|
|
ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "container %q not found", config.IpcMode.Container())
|
|
}
|
|
secopts, err := label.DupSecOpt(ctr.ProcessLabel())
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
|
|
}
|
|
labelOpts = append(labelOpts, secopts...)
|
|
}
|
|
|
|
for _, opt := range securityOpts {
|
|
if opt == "no-new-privileges" {
|
|
config.NoNewPrivs = true
|
|
} else {
|
|
con := strings.SplitN(opt, "=", 2)
|
|
if len(con) != 2 {
|
|
return fmt.Errorf("Invalid --security-opt 1: %q", opt)
|
|
}
|
|
|
|
switch con[0] {
|
|
case "label":
|
|
labelOpts = append(labelOpts, con[1])
|
|
case "apparmor":
|
|
config.ApparmorProfile = con[1]
|
|
case "seccomp":
|
|
config.SeccompProfilePath = con[1]
|
|
default:
|
|
return fmt.Errorf("Invalid --security-opt 2: %q", opt)
|
|
}
|
|
}
|
|
}
|
|
|
|
if config.SeccompProfilePath == "" {
|
|
if _, err := os.Stat(libpod.SeccompOverridePath); err == nil {
|
|
config.SeccompProfilePath = libpod.SeccompOverridePath
|
|
} else {
|
|
if !os.IsNotExist(err) {
|
|
return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompOverridePath)
|
|
}
|
|
if _, err := os.Stat(libpod.SeccompDefaultPath); err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompDefaultPath)
|
|
}
|
|
} else {
|
|
config.SeccompProfilePath = libpod.SeccompDefaultPath
|
|
}
|
|
}
|
|
}
|
|
config.LabelOpts = labelOpts
|
|
return nil
|
|
}
|
|
|
|
func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string {
|
|
entrypoint := []string{}
|
|
if c.IsSet("entrypoint") {
|
|
// Force entrypoint to ""
|
|
if c.String("entrypoint") == "" {
|
|
return entrypoint
|
|
}
|
|
// Check if entrypoint specified is json
|
|
if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil {
|
|
return entrypoint
|
|
}
|
|
// Return entrypoint as a single command
|
|
return []string{c.String("entrypoint")}
|
|
}
|
|
if data != nil {
|
|
return data.Config.Entrypoint
|
|
}
|
|
return entrypoint
|
|
}
|
|
|
|
func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) {
|
|
pod, err := runtime.LookupPod(podName)
|
|
if err != nil {
|
|
return namespaces, err
|
|
}
|
|
podInfraID, err := pod.InfraContainerID()
|
|
if err != nil {
|
|
return namespaces, err
|
|
}
|
|
if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) {
|
|
namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID)
|
|
}
|
|
if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) {
|
|
namespaces["net"] = fmt.Sprintf("container:%s", podInfraID)
|
|
}
|
|
if (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) {
|
|
namespaces["user"] = fmt.Sprintf("container:%s", podInfraID)
|
|
}
|
|
if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) {
|
|
namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID)
|
|
}
|
|
if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) {
|
|
namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID)
|
|
}
|
|
return namespaces, nil
|
|
}
|
|
|
|
// Parses CLI options related to container creation into a config which can be
|
|
// parsed into an OCI runtime spec
|
|
func ParseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) {
|
|
var (
|
|
inputCommand, command []string
|
|
memoryLimit, memoryReservation, memorySwap, memoryKernel int64
|
|
blkioWeight uint16
|
|
namespaces map[string]string
|
|
)
|
|
if c.IsSet("restart") {
|
|
return nil, errors.Errorf("--restart option is not supported.\nUse systemd unit files for restarting containers")
|
|
}
|
|
|
|
idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.String("mac-address") != "" {
|
|
return nil, errors.Errorf("--mac-address option not currently supported")
|
|
}
|
|
|
|
imageID := ""
|
|
|
|
inputCommand = c.InputArgs[1:]
|
|
if data != nil {
|
|
imageID = data.ID
|
|
}
|
|
|
|
rootfs := ""
|
|
if c.Bool("rootfs") {
|
|
rootfs = c.InputArgs[0]
|
|
}
|
|
|
|
sysctl, err := validateSysctl(c.StringSlice("sysctl"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for sysctl")
|
|
}
|
|
|
|
if c.String("memory") != "" {
|
|
memoryLimit, err = units.RAMInBytes(c.String("memory"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for memory")
|
|
}
|
|
}
|
|
if c.String("memory-reservation") != "" {
|
|
memoryReservation, err = units.RAMInBytes(c.String("memory-reservation"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for memory-reservation")
|
|
}
|
|
}
|
|
if c.String("memory-swap") != "" {
|
|
memorySwap, err = units.RAMInBytes(c.String("memory-swap"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for memory-swap")
|
|
}
|
|
}
|
|
if c.String("kernel-memory") != "" {
|
|
memoryKernel, err = units.RAMInBytes(c.String("kernel-memory"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for kernel-memory")
|
|
}
|
|
}
|
|
if c.String("blkio-weight") != "" {
|
|
u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid value for blkio-weight")
|
|
}
|
|
blkioWeight = uint16(u)
|
|
}
|
|
var mountList []spec.Mount
|
|
if mountList, err = parseMounts(c.StringArray("mount")); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = parseVolumes(c.StringArray("volume")); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tty := c.Bool("tty")
|
|
|
|
if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed {
|
|
return nil, errors.Errorf("--cpu-period and --cpus cannot be set together")
|
|
}
|
|
if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed {
|
|
return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together")
|
|
}
|
|
|
|
if c.Bool("no-hosts") && c.Flag("add-host").Changed {
|
|
return nil, errors.Errorf("--no-hosts and --add-host cannot be set together")
|
|
}
|
|
|
|
// EXPOSED PORTS
|
|
var portBindings map[nat.Port][]nat.PortBinding
|
|
if data != nil {
|
|
portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.Config.ExposedPorts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Kernel Namespaces
|
|
// TODO Fix handling of namespace from pod
|
|
// Instead of integrating here, should be done in libpod
|
|
// However, that also involves setting up security opts
|
|
// when the pod's namespace is integrated
|
|
namespaceNet := c.String("network")
|
|
if c.Flag("net").Changed {
|
|
namespaceNet = c.String("net")
|
|
}
|
|
namespaces = map[string]string{
|
|
"pid": c.String("pid"),
|
|
"net": namespaceNet,
|
|
"ipc": c.String("ipc"),
|
|
"user": c.String("userns"),
|
|
"uts": c.String("uts"),
|
|
}
|
|
|
|
originalPodName := c.String("pod")
|
|
podName := strings.Replace(originalPodName, "new:", "", 1)
|
|
// after we strip out :new, make sure there is something left for a pod name
|
|
if len(podName) < 1 && c.IsSet("pod") {
|
|
return nil, errors.Errorf("new pod name must be at least one character")
|
|
}
|
|
if c.IsSet("pod") {
|
|
if strings.HasPrefix(originalPodName, "new:") {
|
|
if rootless.IsRootless() {
|
|
// To create a new pod, we must immediately create the userns.
|
|
became, ret, err := rootless.BecomeRootInUserNS()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if became {
|
|
os.Exit(ret)
|
|
}
|
|
}
|
|
// pod does not exist; lets make it
|
|
var podOptions []libpod.PodCreateOption
|
|
podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups())
|
|
if len(portBindings) > 0 {
|
|
ociPortBindings, err := cc.NatToOCIPortBindings(portBindings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings))
|
|
}
|
|
|
|
podNsOptions, err := GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ","))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
podOptions = append(podOptions, podNsOptions...)
|
|
// make pod
|
|
pod, err := runtime.NewPod(ctx, podOptions...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logrus.Debugf("pod %s created by new container request", pod.ID())
|
|
|
|
// The container now cannot have port bindings; so we reset the map
|
|
portBindings = make(map[nat.Port][]nat.PortBinding)
|
|
}
|
|
namespaces, err = configurePod(c, runtime, namespaces, podName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
pidMode := ns.PidMode(namespaces["pid"])
|
|
if !cc.Valid(string(pidMode), pidMode) {
|
|
return nil, errors.Errorf("--pid %q is not valid", c.String("pid"))
|
|
}
|
|
|
|
usernsMode := ns.UsernsMode(namespaces["user"])
|
|
if !cc.Valid(string(usernsMode), usernsMode) {
|
|
return nil, errors.Errorf("--userns %q is not valid", namespaces["user"])
|
|
}
|
|
|
|
utsMode := ns.UTSMode(namespaces["uts"])
|
|
if !cc.Valid(string(utsMode), utsMode) {
|
|
return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"])
|
|
}
|
|
|
|
ipcMode := ns.IpcMode(namespaces["ipc"])
|
|
if !cc.Valid(string(ipcMode), ipcMode) {
|
|
return nil, errors.Errorf("--ipc %q is not valid", ipcMode)
|
|
}
|
|
|
|
// Make sure if network is set to container namespace, port binding is not also being asked for
|
|
netMode := ns.NetworkMode(namespaces["net"])
|
|
if netMode.IsContainer() {
|
|
if len(portBindings) > 0 {
|
|
return nil, errors.Errorf("cannot set port bindings on an existing container network namespace")
|
|
}
|
|
}
|
|
|
|
// USER
|
|
user := c.String("user")
|
|
if user == "" {
|
|
if data == nil {
|
|
user = "0"
|
|
} else {
|
|
user = data.Config.User
|
|
}
|
|
}
|
|
|
|
// STOP SIGNAL
|
|
stopSignal := syscall.SIGTERM
|
|
signalString := ""
|
|
if data != nil {
|
|
signalString = data.Config.StopSignal
|
|
}
|
|
if c.IsSet("stop-signal") {
|
|
signalString = c.String("stop-signal")
|
|
}
|
|
if signalString != "" {
|
|
stopSignal, err = signal.ParseSignal(signalString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// ENVIRONMENT VARIABLES
|
|
env := defaultEnvVariables
|
|
if data != nil {
|
|
for _, e := range data.Config.Env {
|
|
split := strings.SplitN(e, "=", 2)
|
|
if len(split) > 1 {
|
|
env[split[0]] = split[1]
|
|
} else {
|
|
env[split[0]] = ""
|
|
}
|
|
}
|
|
}
|
|
if err := parse.ReadKVStrings(env, c.StringSlice("env-file"), c.StringArray("env")); err != nil {
|
|
return nil, errors.Wrapf(err, "unable to process environment variables")
|
|
}
|
|
|
|
// LABEL VARIABLES
|
|
labels, err := GetAllLabels(c.StringSlice("label-file"), c.StringArray("label"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to process labels")
|
|
}
|
|
if data != nil {
|
|
for key, val := range data.Config.Labels {
|
|
if _, ok := labels[key]; !ok {
|
|
labels[key] = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// ANNOTATIONS
|
|
annotations := make(map[string]string)
|
|
// First, add our default annotations
|
|
annotations[ann.ContainerType] = "sandbox"
|
|
annotations[ann.TTY] = "false"
|
|
if tty {
|
|
annotations[ann.TTY] = "true"
|
|
}
|
|
if data != nil {
|
|
// Next, add annotations from the image
|
|
for key, value := range data.Annotations {
|
|
annotations[key] = value
|
|
}
|
|
}
|
|
// Last, add user annotations
|
|
for _, annotation := range c.StringSlice("annotation") {
|
|
splitAnnotation := strings.SplitN(annotation, "=", 2)
|
|
if len(splitAnnotation) < 2 {
|
|
return nil, errors.Errorf("Annotations must be formatted KEY=VALUE")
|
|
}
|
|
annotations[splitAnnotation[0]] = splitAnnotation[1]
|
|
}
|
|
|
|
// WORKING DIRECTORY
|
|
workDir := "/"
|
|
if c.IsSet("workdir") || c.IsSet("w") {
|
|
workDir = c.String("workdir")
|
|
} else if data != nil && data.Config.WorkingDir != "" {
|
|
workDir = data.Config.WorkingDir
|
|
}
|
|
|
|
entrypoint := configureEntrypoint(c, data)
|
|
// Build the command
|
|
// If we have an entry point, it goes first
|
|
if len(entrypoint) > 0 {
|
|
command = entrypoint
|
|
}
|
|
if len(inputCommand) > 0 {
|
|
// User command overrides data CMD
|
|
command = append(command, inputCommand...)
|
|
} else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") {
|
|
// If not user command, add CMD
|
|
command = append(command, data.Config.Cmd...)
|
|
}
|
|
|
|
if data != nil && len(command) == 0 {
|
|
return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image")
|
|
}
|
|
|
|
// SHM Size
|
|
shmSize, err := units.FromHumanSize(c.String("shm-size"))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to translate --shm-size")
|
|
}
|
|
|
|
// Verify the additional hosts are in correct format
|
|
for _, host := range c.StringSlice("add-host") {
|
|
if _, err := parse.ValidateExtraHost(host); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Check for . and dns-search domains
|
|
if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 {
|
|
return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
|
|
}
|
|
|
|
// Validate domains are good
|
|
for _, dom := range c.StringSlice("dns-search") {
|
|
if _, err := parse.ValidateDomain(dom); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var ImageVolumes map[string]struct{}
|
|
if data != nil && c.String("image-volume") != "ignore" {
|
|
ImageVolumes = data.Config.Volumes
|
|
}
|
|
|
|
var imageVolType = map[string]string{
|
|
"bind": "",
|
|
"tmpfs": "",
|
|
"ignore": "",
|
|
}
|
|
if _, ok := imageVolType[c.String("image-volume")]; !ok {
|
|
return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume"))
|
|
}
|
|
|
|
var systemd bool
|
|
if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) {
|
|
systemd = true
|
|
if signalString == "" {
|
|
stopSignal, err = signal.ParseSignal("RTMIN+3")
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error parsing systemd signal")
|
|
}
|
|
}
|
|
}
|
|
// This is done because cobra cannot have two aliased flags. So we have to check
|
|
// both
|
|
network := c.String("network")
|
|
if c.Flag("net").Changed {
|
|
network = c.String("net")
|
|
}
|
|
|
|
var memorySwappiness int64
|
|
if c.Flags().Lookup("memory-swappiness") != nil {
|
|
memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness")
|
|
}
|
|
config := &cc.CreateConfig{
|
|
Runtime: runtime,
|
|
Annotations: annotations,
|
|
BuiltinImgVolumes: ImageVolumes,
|
|
ConmonPidFile: c.String("conmon-pidfile"),
|
|
ImageVolumeType: c.String("image-volume"),
|
|
CapAdd: c.StringSlice("cap-add"),
|
|
CapDrop: c.StringSlice("cap-drop"),
|
|
CgroupParent: c.String("cgroup-parent"),
|
|
Command: command,
|
|
Detach: c.Bool("detach"),
|
|
Devices: c.StringSlice("device"),
|
|
DNSOpt: c.StringSlice("dns-opt"),
|
|
DNSSearch: c.StringSlice("dns-search"),
|
|
DNSServers: c.StringSlice("dns"),
|
|
Entrypoint: entrypoint,
|
|
Env: env,
|
|
//ExposedPorts: ports,
|
|
GroupAdd: c.StringSlice("group-add"),
|
|
Hostname: c.String("hostname"),
|
|
HostAdd: c.StringSlice("add-host"),
|
|
NoHosts: c.Bool("no-hosts"),
|
|
IDMappings: idmappings,
|
|
Image: imageName,
|
|
ImageID: imageID,
|
|
Interactive: c.Bool("interactive"),
|
|
//IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6
|
|
IPAddress: c.String("ip"),
|
|
Labels: labels,
|
|
//LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet
|
|
LogDriver: c.String("log-driver"),
|
|
LogDriverOpt: c.StringSlice("log-opt"),
|
|
MacAddress: c.String("mac-address"),
|
|
Name: c.String("name"),
|
|
Network: network,
|
|
//NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman?
|
|
IpcMode: ipcMode,
|
|
NetMode: netMode,
|
|
UtsMode: utsMode,
|
|
PidMode: pidMode,
|
|
Pod: podName,
|
|
Privileged: c.Bool("privileged"),
|
|
Publish: c.StringSlice("publish"),
|
|
PublishAll: c.Bool("publish-all"),
|
|
PortBindings: portBindings,
|
|
Quiet: c.Bool("quiet"),
|
|
ReadOnlyRootfs: c.Bool("read-only"),
|
|
Resources: cc.CreateResourceConfig{
|
|
BlkioWeight: blkioWeight,
|
|
BlkioWeightDevice: c.StringSlice("blkio-weight-device"),
|
|
CPUShares: c.Uint64("cpu-shares"),
|
|
CPUPeriod: c.Uint64("cpu-period"),
|
|
CPUsetCPUs: c.String("cpuset-cpus"),
|
|
CPUsetMems: c.String("cpuset-mems"),
|
|
CPUQuota: c.Int64("cpu-quota"),
|
|
CPURtPeriod: c.Uint64("cpu-rt-period"),
|
|
CPURtRuntime: c.Int64("cpu-rt-runtime"),
|
|
CPUs: c.Float64("cpus"),
|
|
DeviceReadBps: c.StringSlice("device-read-bps"),
|
|
DeviceReadIOps: c.StringSlice("device-read-iops"),
|
|
DeviceWriteBps: c.StringSlice("device-write-bps"),
|
|
DeviceWriteIOps: c.StringSlice("device-write-iops"),
|
|
DisableOomKiller: c.Bool("oom-kill-disable"),
|
|
ShmSize: shmSize,
|
|
Memory: memoryLimit,
|
|
MemoryReservation: memoryReservation,
|
|
MemorySwap: memorySwap,
|
|
MemorySwappiness: int(memorySwappiness),
|
|
KernelMemory: memoryKernel,
|
|
OomScoreAdj: c.Int("oom-score-adj"),
|
|
PidsLimit: c.Int64("pids-limit"),
|
|
Ulimit: c.StringSlice("ulimit"),
|
|
},
|
|
Rm: c.Bool("rm"),
|
|
StopSignal: stopSignal,
|
|
StopTimeout: c.Uint("stop-timeout"),
|
|
Sysctl: sysctl,
|
|
Systemd: systemd,
|
|
Tmpfs: c.StringSlice("tmpfs"),
|
|
Tty: tty,
|
|
User: user,
|
|
UsernsMode: usernsMode,
|
|
Mounts: mountList,
|
|
Volumes: c.StringArray("volume"),
|
|
WorkDir: workDir,
|
|
Rootfs: rootfs,
|
|
VolumesFrom: c.StringSlice("volumes-from"),
|
|
Syslog: c.GlobalFlags.Syslog,
|
|
}
|
|
if c.Bool("init") {
|
|
initPath := c.String("init-path")
|
|
if initPath == "" {
|
|
rtc, err := runtime.GetConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
initPath = rtc.InitPath
|
|
}
|
|
if err := config.AddContainerInitBinary(initPath); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if config.Privileged {
|
|
config.LabelOpts = label.DisableSecOpt()
|
|
} else {
|
|
if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
config.SecurityOpts = c.StringArray("security-opt")
|
|
warnings, err := verifyContainerResources(config, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, warning := range warnings {
|
|
fmt.Fprintln(os.Stderr, warning)
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
type namespace interface {
|
|
IsContainer() bool
|
|
Container() string
|
|
}
|
|
|
|
func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) {
|
|
runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
options, err := createConfig.GetContainerCreateOptions(r, pod)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ctr, err := r.NewContainer(ctx, runtimeSpec, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
createConfigJSON, err := json.Marshal(createConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil {
|
|
return nil, err
|
|
}
|
|
return ctr, nil
|
|
}
|
|
|
|
var defaultEnvVariables = map[string]string{
|
|
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"TERM": "xterm",
|
|
}
|
|
|
|
func makeHealthCheckFromCli(c *cliconfig.PodmanCommand) (*manifest.Schema2HealthConfig, error) {
|
|
inCommand := c.String("healthcheck-command")
|
|
inInterval := c.String("healthcheck-interval")
|
|
inRetries := c.Uint("healthcheck-retries")
|
|
inTimeout := c.String("healthcheck-timeout")
|
|
inStartPeriod := c.String("healthcheck-start-period")
|
|
|
|
// Every healthcheck requires a command
|
|
if len(inCommand) == 0 {
|
|
return nil, errors.New("Must define a healthcheck command for all healthchecks")
|
|
}
|
|
|
|
cmd, err := shlex.Split(inCommand)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to parse healthcheck command")
|
|
}
|
|
hc := manifest.Schema2HealthConfig{
|
|
Test: cmd,
|
|
}
|
|
|
|
if inInterval == "disable" {
|
|
inInterval = "0"
|
|
}
|
|
intervalDuration, err := time.ParseDuration(inInterval)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", inInterval)
|
|
}
|
|
|
|
hc.Interval = intervalDuration
|
|
|
|
if inRetries < 1 {
|
|
return nil, errors.New("healthcheck-retries must be greater than 0.")
|
|
}
|
|
hc.Retries = int(inRetries)
|
|
timeoutDuration, err := time.ParseDuration(inTimeout)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", inTimeout)
|
|
}
|
|
if timeoutDuration < time.Duration(time.Second*1) {
|
|
return nil, errors.New("healthcheck-timeout must be at least 1 second")
|
|
}
|
|
hc.Timeout = timeoutDuration
|
|
|
|
startPeriodDuration, err := time.ParseDuration(inStartPeriod)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod)
|
|
}
|
|
if startPeriodDuration < time.Duration(0) {
|
|
return nil, errors.New("healthcheck-start-period must be a 0 seconds or greater")
|
|
}
|
|
hc.StartPeriod = startPeriodDuration
|
|
|
|
return &hc, nil
|
|
}
|