package main import ( "context" "fmt" "os" "strings" "github.com/containers/buildah" buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/sysinfo" "github.com/containers/libpod/pkg/util/camelcase" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var ( json = jsoniter.ConfigCompatibleWithStandardLibrary ) const ( idTruncLength = 12 sizeWithUnitFormat = "(format: `[]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))" ) func splitCamelCase(src string) string { entries := camelcase.Split(src) return strings.Join(entries, " ") } func shortID(id string) string { if len(id) > idTruncLength { return id[:idTruncLength] } return id } // checkAllLatestAndCIDFile checks that --all and --latest are used correctly. // If cidfile is set, also check for the --cidfile flag. func checkAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { argLen := len(args) if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { if !cidfile { return errors.New("unable to lookup values for 'latest' or 'all'") } else if c.Flags().Lookup("cidfile") == nil { return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") } } specifiedAll, _ := c.Flags().GetBool("all") specifiedLatest, _ := c.Flags().GetBool("latest") specifiedCIDFile := false if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { specifiedCIDFile = true } if specifiedCIDFile && (specifiedAll || specifiedLatest) { return errors.Errorf("--all, --latest and --cidfile cannot be used together") } else if specifiedAll && specifiedLatest { return errors.Errorf("--all and --latest cannot be used together") } if ignoreArgLen { return nil } if (argLen > 0) && (specifiedAll || specifiedLatest) { return errors.Errorf("no arguments are needed with --all or --latest") } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) { return errors.Errorf("no arguments are needed with --all, --latest or --cidfile") } if specifiedCIDFile { return nil } if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile { return errors.Errorf("you must provide at least one name or id") } return nil } // noSubArgs checks that there are no further positional parameters func noSubArgs(c *cobra.Command, args []string) error { if len(args) > 0 { return errors.Errorf("`%s` takes no arguments", c.CommandPath()) } return nil } func commandRunE() func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { if len(args) > 0 { return errors.Errorf("unrecognized command `%s %s`\nTry '%s --help' for more information.", cmd.CommandPath(), args[0], cmd.CommandPath()) } else { return errors.Errorf("missing command '%s COMMAND'\nTry '%s --help' for more information.", cmd.CommandPath(), cmd.CommandPath()) } } } // getContext returns a non-nil, empty context func getContext() context.Context { if Ctx != nil { return Ctx } return context.TODO() } func getDefaultNetwork() string { if rootless.IsRootless() { return "slirp4netns" } return "bridge" } func getNetFlags() *pflag.FlagSet { netFlags := pflag.FlagSet{} netFlags.StringSlice( "add-host", []string{}, "Add a custom host-to-IP mapping (host:ip) (default [])", ) netFlags.StringSlice( "dns", []string{}, "Set custom DNS servers", ) netFlags.StringSlice( "dns-opt", []string{}, "Set custom DNS options", ) netFlags.StringSlice( "dns-search", []string{}, "Set custom DNS search domains", ) netFlags.String( "ip", "", "Specify a static IPv4 address for the container", ) netFlags.String( "mac-address", "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)", ) netFlags.String( "network", getDefaultNetwork(), "Connect a container to a network", ) netFlags.StringSliceP( "publish", "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])", ) netFlags.Bool( "no-hosts", false, "Do not create /etc/hosts within the container, instead use the version from the image", ) return &netFlags } func getCreateFlags(c *cliconfig.PodmanCommand) { createFlags := c.Flags() createFlags.StringSlice( "annotation", []string{}, "Add annotations to container (key:value) (default [])", ) createFlags.StringSliceP( "attach", "a", []string{}, "Attach to STDIN, STDOUT or STDERR (default [])", ) createFlags.String( "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override", ) createFlags.String( "blkio-weight", "", "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", ) createFlags.StringSlice( "blkio-weight-device", []string{}, "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", ) createFlags.StringSlice( "cap-add", []string{}, "Add capabilities to the container", ) createFlags.StringSlice( "cap-drop", []string{}, "Drop capabilities from the container", ) createFlags.String( "cgroupns", "", "cgroup namespace to use", ) createFlags.String( "cgroups", "enabled", `control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`, ) createFlags.String( "cgroup-parent", "", "Optional parent cgroup for the container", ) createFlags.String( "cidfile", "", "Write the container ID to the file", ) createFlags.String( "conmon-pidfile", "", "Path to the file that will receive the PID of conmon", ) createFlags.Uint64( "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period", ) createFlags.Int64( "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota", ) createFlags.Uint64( "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds", ) createFlags.Int64( "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds", ) createFlags.Uint64( "cpu-shares", 0, "CPU shares (relative weight)", ) createFlags.Float64( "cpus", 0, "Number of CPUs. The default is 0.000 which means no limit", ) createFlags.String( "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)", ) createFlags.String( "cpuset-mems", "", "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", ) createFlags.BoolP( "detach", "d", false, "Run container in background and print container ID", ) detachKeys := createFlags.String( "detach-keys", define.DefaultDetachKeys, "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-`, where `` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`", ) // Clear the default, the value specified in the config file should have the // priority *detachKeys = "" createFlags.StringSlice( "device", []string{}, "Add a host device to the container (default [])", ) createFlags.StringSlice( "device-cgroup-rule", []string{}, "Add a rule to the cgroup allowed devices list", ) createFlags.StringSlice( "device-read-bps", []string{}, "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", ) createFlags.StringSlice( "device-read-iops", []string{}, "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", ) createFlags.StringSlice( "device-write-bps", []string{}, "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", ) createFlags.StringSlice( "device-write-iops", []string{}, "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", ) createFlags.String( "entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) createFlags.StringArrayP( "env", "e", []string{}, "Set environment variables in container", ) createFlags.Bool( "env-host", false, "Use all current host environment variables in container", ) createFlags.StringSlice( "env-file", []string{}, "Read in a file of environment variables", ) createFlags.StringSlice( "expose", []string{}, "Expose a port or a range of ports (default [])", ) createFlags.StringSlice( "gidmap", []string{}, "GID map to use for the user namespace", ) createFlags.StringSlice( "group-add", []string{}, "Add additional groups to join (default [])", ) createFlags.Bool( "help", false, "", ) createFlags.String( "health-cmd", "", "set a healthcheck command for the container ('none' disables the existing healthcheck)", ) createFlags.String( "health-interval", cliconfig.DefaultHealthCheckInterval, "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", ) createFlags.Uint( "health-retries", cliconfig.DefaultHealthCheckRetries, "the number of retries allowed before a healthcheck is considered to be unhealthy", ) createFlags.String( "health-start-period", cliconfig.DefaultHealthCheckStartPeriod, "the initialization time needed for a container to bootstrap", ) createFlags.String( "health-timeout", cliconfig.DefaultHealthCheckTimeout, "the maximum time allowed to complete the healthcheck before an interval is considered failed", ) createFlags.StringP( "hostname", "h", "", "Set container hostname", ) createFlags.Bool( "http-proxy", true, "Set proxy environment variables in the container based on the host proxy vars", ) createFlags.String( "image-volume", cliconfig.DefaultImageVolume, `Tells podman how to handle the builtin image volumes ("bind"|"tmpfs"|"ignore")`, ) createFlags.Bool( "init", false, "Run an init binary inside the container that forwards signals and reaps processes", ) createFlags.String( "init-path", "", // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) fmt.Sprintf("Path to the container-init binary (default: %q)", define.DefaultInitPath), ) createFlags.BoolP( "interactive", "i", false, "Keep STDIN open even if not attached", ) createFlags.String( "ipc", "", "IPC namespace to use", ) createFlags.String( "kernel-memory", "", "Kernel memory limit "+sizeWithUnitFormat, ) createFlags.StringArrayP( "label", "l", []string{}, "Set metadata on container (default [])", ) createFlags.StringSlice( "label-file", []string{}, "Read in a line delimited file of labels (default [])", ) createFlags.String( "log-driver", "", "Logging driver for the container", ) createFlags.StringSlice( "log-opt", []string{}, "Logging driver options (default [])", ) createFlags.StringP( "memory", "m", "", "Memory limit "+sizeWithUnitFormat, ) createFlags.String( "memory-reservation", "", "Memory soft limit "+sizeWithUnitFormat, ) createFlags.String( "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", ) createFlags.Int64( "memory-swappiness", -1, "Tune container memory swappiness (0 to 100, or -1 for system default)", ) createFlags.String( "name", "", "Assign a name to the container", ) createFlags.Bool( "no-healthcheck", false, "Disable healthchecks on container", ) createFlags.Bool( "oom-kill-disable", false, "Disable OOM Killer", ) createFlags.Int( "oom-score-adj", 0, "Tune the host's OOM preferences (-1000 to 1000)", ) createFlags.String( "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images", ) markFlagHidden(createFlags, "override-arch") createFlags.String( "override-os", "", "use `OS` instead of the running OS for choosing images", ) markFlagHidden(createFlags, "override-os") createFlags.String( "pid", "", "PID namespace to use", ) createFlags.Int64( "pids-limit", sysinfo.GetDefaultPidsLimit(), "Tune container pids limit (set 0 for unlimited)", ) createFlags.String( "pod", "", "Run container in an existing pod", ) createFlags.Bool( "privileged", false, "Give extended privileges to container", ) createFlags.BoolP( "publish-all", "P", false, "Publish all exposed ports to random ports on the host interface", ) createFlags.String( "pull", "missing", `Pull image before creating ("always"|"missing"|"never")`, ) createFlags.BoolP( "quiet", "q", false, "Suppress output information when pulling images", ) createFlags.Bool( "read-only", false, "Make containers root filesystem read-only", ) createFlags.Bool( "read-only-tmpfs", true, "When running containers in read-only mode mount a read-write tmpfs on /run, /tmp and /var/tmp", ) createFlags.String( "restart", "", `Restart policy to apply when a container exits ("always"|"no"|"on-failure")`, ) createFlags.Bool( "rm", false, "Remove container (and pod if created) after exit", ) createFlags.Bool( "rootfs", false, "The first argument is not an image but the rootfs to the exploded container", ) createFlags.StringArray( "security-opt", []string{}, "Security Options (default [])", ) createFlags.String( "shm-size", cliconfig.DefaultShmSize, "Size of /dev/shm "+sizeWithUnitFormat, ) createFlags.String( "stop-signal", "", "Signal to stop a container. Default is SIGTERM", ) createFlags.Uint( "stop-timeout", define.CtrRemoveTimeout, "Timeout (in seconds) to stop a container. Default is 10", ) createFlags.StringSlice( "storage-opt", []string{}, "Storage driver options per container (default [])", ) createFlags.String( "subgidname", "", "Name of range listed in /etc/subgid for use in user namespace", ) createFlags.String( "subuidname", "", "Name of range listed in /etc/subuid for use in user namespace", ) createFlags.StringSlice( "sysctl", []string{}, "Sysctl options (default [])", ) createFlags.String( "systemd", "true", `Run container in systemd mode ("true"|"false"|"always")`, ) createFlags.StringArray( "tmpfs", []string{}, "Mount a temporary filesystem (`tmpfs`) into a container (default [])", ) createFlags.BoolP( "tty", "t", false, "Allocate a pseudo-TTY for container", ) createFlags.StringSlice( "uidmap", []string{}, "UID map to use for the user namespace", ) createFlags.StringSlice( "ulimit", []string{}, "Ulimit options (default [])", ) createFlags.StringP( "user", "u", "", "Username or UID (format: [:])", ) createFlags.String( "userns", os.Getenv("PODMAN_USERNS"), "User namespace to use", ) createFlags.String( "uts", "", "UTS namespace to use", ) createFlags.StringArray( "mount", []string{}, "Attach a filesystem mount to the container (default [])", ) createFlags.StringArrayP( "volume", "v", []string{}, "Bind mount a volume into the container (default [])", ) createFlags.StringSlice( "volumes-from", []string{}, "Mount volumes from the specified container(s) (default [])", ) createFlags.StringP( "workdir", "w", "", "Working directory inside the container", ) createFlags.String( "seccomp-policy", "default", "Policy for selecting a seccomp profile (experimental)", ) } func getFormat(c *cliconfig.PodmanCommand) (string, error) { format := strings.ToLower(c.String("format")) if strings.HasPrefix(format, buildah.OCI) { return buildah.OCIv1ImageManifest, nil } if strings.HasPrefix(format, buildah.DOCKER) { return buildah.Dockerv2ImageManifest, nil } return "", errors.Errorf("unrecognized image type %q", format) } // scrubServer removes 'http://' or 'https://' from the front of the // server/registry string if either is there. This will be mostly used // for user input from 'podman login' and 'podman logout'. func scrubServer(server string) string { server = strings.TrimPrefix(server, "https://") return strings.TrimPrefix(server, "http://") } // HelpTemplate returns the help template for podman commands // This uses the short and long options. // command should not use this. func HelpTemplate() string { return `{{.Short}} Description: {{.Long}} {{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` } // UsageTemplate returns the usage template for podman commands // This blocks the desplaying of the global options. The main podman // command should not use this. func UsageTemplate() string { return `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} Aliases: {{.NameAndAliases}}{{end}}{{if .HasExample}} Examples: {{.Example}}{{end}}{{if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} {{end}} ` }