diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/common/completion.go | 48 | ||||
-rw-r--r-- | cmd/podman/common/create.go | 9 | ||||
-rw-r--r-- | cmd/podman/common/create_opts.go | 116 | ||||
-rw-r--r-- | cmd/podman/common/specgen.go | 2 | ||||
-rw-r--r-- | cmd/podman/common/util.go | 10 | ||||
-rw-r--r-- | cmd/podman/containers/create.go | 21 | ||||
-rw-r--r-- | cmd/podman/containers/run.go | 2 | ||||
-rw-r--r-- | cmd/podman/generate/kube.go | 5 | ||||
-rw-r--r-- | cmd/podman/images/build.go | 25 | ||||
-rw-r--r-- | cmd/podman/images/buildx.go | 29 | ||||
-rw-r--r-- | cmd/podman/images/scp.go | 2 | ||||
-rw-r--r-- | cmd/podman/play/kube.go | 3 | ||||
-rw-r--r-- | cmd/podman/pods/create.go | 10 | ||||
-rw-r--r-- | cmd/podman/registry/config.go | 7 | ||||
-rw-r--r-- | cmd/podman/root.go | 41 | ||||
-rw-r--r-- | cmd/podman/shell_completion_test.go | 4 | ||||
-rw-r--r-- | cmd/podman/volumes/export.go | 96 | ||||
-rw-r--r-- | cmd/podman/volumes/import.go | 97 |
18 files changed, 456 insertions, 71 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 08b2f6235..9a4524b46 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -323,6 +323,18 @@ func prefixSlice(pre string, slice []string) []string { return slice } +func suffixCompSlice(suf string, slice []string) []string { + for i := range slice { + split := strings.SplitN(slice[i], "\t", 2) + if len(split) > 1 { + slice[i] = split[0] + suf + "\t" + split[1] + } else { + slice[i] = slice[i] + suf + } + } + return slice +} + func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra.ShellCompDirective) { suggestions := make([]string, 0, len(k)) directive := cobra.ShellCompDirectiveNoFileComp @@ -664,6 +676,42 @@ func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete return suggestions, cobra.ShellCompDirectiveNoFileComp } +// AutocompleteScp returns a list of connections, images, or both, depending on the amount of arguments +func AutocompleteScp(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + switch len(args) { + case 0: + split := strings.SplitN(toComplete, "::", 2) + if len(split) > 1 { + imageSuggestions, _ := getImages(cmd, split[1]) + return prefixSlice(split[0]+"::", imageSuggestions), cobra.ShellCompDirectiveNoFileComp + } + connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete) + imageSuggestions, _ := getImages(cmd, toComplete) + totalSuggestions := append(suffixCompSlice("::", connectionSuggestions), imageSuggestions...) + directive := cobra.ShellCompDirectiveNoFileComp + // if we have connections do not add a space after the completion + if len(connectionSuggestions) > 0 { + directive = cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace + } + return totalSuggestions, directive + case 1: + split := strings.SplitN(args[0], "::", 2) + if len(split) > 1 { + if len(split[1]) > 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + imageSuggestions, _ := getImages(cmd, toComplete) + return imageSuggestions, cobra.ShellCompDirectiveNoFileComp + } + connectionSuggestions, _ := AutocompleteSystemConnections(cmd, args, toComplete) + return suffixCompSlice("::", connectionSuggestions), cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + /* -------------- Flags ----------------- */ // AutocompleteDetachKeys - Autocomplete detach-keys options. diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 602ad5d94..401cf2e09 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -544,6 +544,15 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { ) _ = cmd.RegisterFlagCompletionFunc(podIDFileFlagName, completion.AutocompleteDefault) + // Flag for TLS verification, so that `run` and `create` commands can make use of it. + // Make sure to use `=` while using this flag i.e `--tls-verify=false/true` + tlsVerifyFlagName := "tls-verify" + createFlags.BoolVar( + &cf.TLSVerify, + tlsVerifyFlagName, true, + "Require HTTPS and verify certificates when contacting registries for pulling images", + ) + createFlags.BoolVar( &cf.Privileged, "privileged", false, diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 0a969bfd2..c94f46cf2 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -10,6 +10,7 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/domain/entities" @@ -112,6 +113,7 @@ type ContainerCLIOpts struct { Sysctl []string Systemd string Timeout uint + TLSVerify bool TmpFS []string TTY bool Timezone string @@ -149,7 +151,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c cappDrop []string entrypoint *string init bool - specPorts []specgen.PortMapping + specPorts []types.PortMapping ) if cc.HostConfig.Init != nil { @@ -239,7 +241,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c if err != nil { return nil, nil, err } - tmpPort := specgen.PortMapping{ + tmpPort := types.PortMapping{ HostIP: pb.HostIP, ContainerPort: uint16(port.Int()), HostPort: uint16(hostport), @@ -356,51 +358,55 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c CPUSetMems: cc.HostConfig.CpusetMems, // Detach: false, // don't need // DetachKeys: "", // don't need - Devices: devices, - DeviceCGroupRule: nil, - DeviceReadBPs: readBps, - DeviceReadIOPs: readIops, - DeviceWriteBPs: writeBps, - DeviceWriteIOPs: writeIops, - Entrypoint: entrypoint, - Env: cc.Config.Env, - Expose: expose, - GroupAdd: cc.HostConfig.GroupAdd, - Hostname: cc.Config.Hostname, - ImageVolume: "bind", - Init: init, - Interactive: cc.Config.OpenStdin, - IPC: string(cc.HostConfig.IpcMode), - Label: stringMaptoArray(cc.Config.Labels), - LogDriver: cc.HostConfig.LogConfig.Type, - LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), - Name: cc.Name, - OOMScoreAdj: cc.HostConfig.OomScoreAdj, - Arch: "", - OS: "", - Variant: "", - PID: string(cc.HostConfig.PidMode), - PIDsLimit: cc.HostConfig.PidsLimit, - Privileged: cc.HostConfig.Privileged, - PublishAll: cc.HostConfig.PublishAllPorts, - Quiet: false, - ReadOnly: cc.HostConfig.ReadonlyRootfs, - ReadOnlyTmpFS: true, // podman default - Rm: cc.HostConfig.AutoRemove, - SecurityOpt: cc.HostConfig.SecurityOpt, - StopSignal: cc.Config.StopSignal, - StorageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), - Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), - Systemd: "true", // podman default - TmpFS: parsedTmp, - TTY: cc.Config.Tty, - User: cc.Config.User, - UserNS: string(cc.HostConfig.UsernsMode), - UTS: string(cc.HostConfig.UTSMode), - Mount: mounts, - VolumesFrom: cc.HostConfig.VolumesFrom, - Workdir: cc.Config.WorkingDir, - Net: &netInfo, + Devices: devices, + DeviceCGroupRule: nil, + DeviceReadBPs: readBps, + DeviceReadIOPs: readIops, + DeviceWriteBPs: writeBps, + DeviceWriteIOPs: writeIops, + Entrypoint: entrypoint, + Env: cc.Config.Env, + Expose: expose, + GroupAdd: cc.HostConfig.GroupAdd, + Hostname: cc.Config.Hostname, + ImageVolume: "bind", + Init: init, + Interactive: cc.Config.OpenStdin, + IPC: string(cc.HostConfig.IpcMode), + Label: stringMaptoArray(cc.Config.Labels), + LogDriver: cc.HostConfig.LogConfig.Type, + LogOptions: stringMaptoArray(cc.HostConfig.LogConfig.Config), + Name: cc.Name, + OOMScoreAdj: cc.HostConfig.OomScoreAdj, + Arch: "", + OS: "", + Variant: "", + PID: string(cc.HostConfig.PidMode), + PIDsLimit: cc.HostConfig.PidsLimit, + Privileged: cc.HostConfig.Privileged, + PublishAll: cc.HostConfig.PublishAllPorts, + Quiet: false, + ReadOnly: cc.HostConfig.ReadonlyRootfs, + ReadOnlyTmpFS: true, // podman default + Rm: cc.HostConfig.AutoRemove, + SecurityOpt: cc.HostConfig.SecurityOpt, + StopSignal: cc.Config.StopSignal, + StorageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), + Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), + Systemd: "true", // podman default + TmpFS: parsedTmp, + TTY: cc.Config.Tty, + User: cc.Config.User, + UserNS: string(cc.HostConfig.UsernsMode), + UTS: string(cc.HostConfig.UTSMode), + Mount: mounts, + VolumesFrom: cc.HostConfig.VolumesFrom, + Workdir: cc.Config.WorkingDir, + Net: &netInfo, + HealthInterval: DefaultHealthCheckInterval, + HealthRetries: DefaultHealthCheckRetries, + HealthTimeout: DefaultHealthCheckTimeout, + HealthStartPeriod: DefaultHealthCheckStartPeriod, } if !rootless.IsRootless() { var ulimits []string @@ -527,10 +533,18 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c finCmd = finCmd[:len(finCmd)-1] } cliOpts.HealthCmd = finCmd - cliOpts.HealthInterval = cc.Config.Healthcheck.Interval.String() - cliOpts.HealthRetries = uint(cc.Config.Healthcheck.Retries) - cliOpts.HealthStartPeriod = cc.Config.Healthcheck.StartPeriod.String() - cliOpts.HealthTimeout = cc.Config.Healthcheck.Timeout.String() + if cc.Config.Healthcheck.Interval > 0 { + cliOpts.HealthInterval = cc.Config.Healthcheck.Interval.String() + } + if cc.Config.Healthcheck.Retries > 0 { + cliOpts.HealthRetries = uint(cc.Config.Healthcheck.Retries) + } + if cc.Config.Healthcheck.StartPeriod > 0 { + cliOpts.HealthStartPeriod = cc.Config.Healthcheck.StartPeriod.String() + } + if cc.Config.Healthcheck.Timeout > 0 { + cliOpts.HealthTimeout = cc.Config.Healthcheck.Timeout.String() + } } // specgen assumes the image name is arg[0] diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 8d6a21cb7..59d32f568 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -685,7 +685,7 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start concat := "" if cmdArr[0] == "CMD" || cmdArr[0] == "none" { // this is for compat, we are already split properly for most compat cases cmdArr = strings.Fields(inCmd) - } else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, wont contain the keywords + } else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, won't contain the keywords if isArr && len(cmdArr) > 1 { // an array of consecutive commands cmdArr = append([]string{"CMD"}, cmdArr...) } else { // one singular command diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index 6a0af4dff..cdfff9d6f 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/containers/podman/v3/pkg/specgen" + "github.com/containers/podman/v3/libpod/network/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -90,10 +90,10 @@ func createExpose(expose []string) (map[uint16]string, error) { } // CreatePortBindings iterates ports mappings into SpecGen format. -func CreatePortBindings(ports []string) ([]specgen.PortMapping, error) { +func CreatePortBindings(ports []string) ([]types.PortMapping, error) { // --publish is formatted as follows: // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] - toReturn := make([]specgen.PortMapping, 0, len(ports)) + toReturn := make([]types.PortMapping, 0, len(ports)) for _, p := range ports { var ( @@ -169,8 +169,8 @@ func CreatePortBindings(ports []string) ([]specgen.PortMapping, error) { // parseSplitPort parses individual components of the --publish flag to produce // a single port mapping in SpecGen format. -func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) { - newPort := specgen.PortMapping{} +func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (types.PortMapping, error) { + newPort := types.PortMapping{} if ctrPort == "" { return newPort, errors.Errorf("must provide a non-empty container port to publish") } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 895736144..7583a024e 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -10,9 +10,11 @@ import ( "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" + "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/util" @@ -95,7 +97,7 @@ func create(cmd *cobra.Command, args []string) error { var ( err error ) - cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "") + cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "" && cliVals.PodIDFile == "") if err != nil { return err } @@ -105,8 +107,8 @@ func create(cmd *cobra.Command, args []string) error { if !cmd.Flags().Changed("pod") { return errors.New("must specify pod value with init-ctr") } - if !util.StringInSlice(initctr, []string{"always", "oneshot"}) { - return errors.New("init-ctr value must be 'always' or 'oneshot'") + if !util.StringInSlice(initctr, []string{define.AlwaysInitContainer, define.OneShotInitContainer}) { + return errors.Errorf("init-ctr value must be '%s' or '%s'", define.AlwaysInitContainer, define.OneShotInitContainer) } cliVals.InitContainerType = initctr } @@ -184,6 +186,9 @@ func createInit(c *cobra.Command) error { if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { return errors.Errorf("--cpu-quota and --cpus cannot be set together") } + if c.Flag("pod").Changed && !strings.HasPrefix(c.Flag("pod").Value.String(), "new:") && c.Flag("userns").Changed { + return errors.Errorf("--userns and --pod cannot be set together") + } noHosts, err := c.Flags().GetBool("no-hosts") if err != nil { @@ -257,7 +262,7 @@ func createInit(c *cobra.Command) error { } func pullImage(imageName string) (string, error) { - pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull) + pullPolicy, err := config.ParsePullPolicy(cliVals.Pull) if err != nil { return "", err } @@ -283,6 +288,7 @@ func pullImage(imageName string) (string, error) { Variant: cliVals.Variant, SignaturePolicy: cliVals.SignaturePolicy, PullPolicy: pullPolicy, + SkipTLSVerify: types.NewOptionalBool(!cliVals.TLSVerify), // If Flag changed for TLS Verification }) if pullErr != nil { return "", pullErr @@ -309,6 +315,12 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions if len(podName) < 1 { return nil, errors.Errorf("new pod name must be at least one character") } + + userns, err := specgen.ParseUserNamespace(cliVals.UserNS) + if err != nil { + return nil, err + } + createOptions := entities.PodCreateOptions{ Name: podName, Infra: true, @@ -318,6 +330,7 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions Cpus: cliVals.CPUS, CpusetCpus: cliVals.CPUSetCPUs, Pid: cliVals.PID, + Userns: userns, } // Unset config values we passed to the pod to prevent them being used twice for the container and pod. s.ContainerBasicConfig.Hostname = "" diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 579af4eb1..830d1de7f 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -106,7 +106,7 @@ func init() { func run(cmd *cobra.Command, args []string) error { var err error - cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "") + cliVals.Net, err = common.NetFlagsToNetOptions(cmd, cliVals.Pod == "" && cliVals.PodIDFile == "") if err != nil { return err } diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go index b4c9f9146..60b8f0af0 100644 --- a/cmd/podman/generate/kube.go +++ b/cmd/podman/generate/kube.go @@ -2,6 +2,7 @@ package pods import ( "fmt" + "io" "io/ioutil" "os" @@ -61,6 +62,10 @@ func kube(cmd *cobra.Command, args []string) error { if err != nil { return err } + if r, ok := report.Reader.(io.ReadCloser); ok { + defer r.Close() + } + if cmd.Flags().Changed("filename") { if _, err := os.Stat(kubeFile); err == nil { return errors.Errorf("cannot write to %q; file exists", kubeFile) diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 3aeba6fb0..a1a28b809 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -67,6 +67,18 @@ var ( podman image build --layers --force-rm --tag imageName .`, } + buildxBuildCmd = &cobra.Command{ + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + ValidArgsFunction: buildCmd.ValidArgsFunction, + Example: `podman buildx build . + podman buildx build --creds=username:password -t imageName -f Containerfile.simple . + podman buildx build --layers --force-rm --tag imageName .`, + } + buildOpts = buildFlagsWrapper{} ) @@ -91,11 +103,24 @@ func init() { Parent: imageCmd, }) buildFlags(imageBuildCmd) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: buildxBuildCmd, + Parent: buildxCmd, + }) + buildFlags(buildxBuildCmd) } func buildFlags(cmd *cobra.Command) { flags := cmd.Flags() + // buildx build --load ignored, but added for compliance + flags.Bool("load", false, "buildx --load") + _ = flags.MarkHidden("load") + + // buildx build --progress ignored, but added for compliance + flags.String("progress", "auto", "buildx --progress") + _ = flags.MarkHidden("progress") + // Podman flags flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") diff --git a/cmd/podman/images/buildx.go b/cmd/podman/images/buildx.go new file mode 100644 index 000000000..5c8e5aaa0 --- /dev/null +++ b/cmd/podman/images/buildx.go @@ -0,0 +1,29 @@ +package images + +import ( + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/cmd/podman/validate" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _buildx_ + // This is a hidden command, which was added to make converting + // from Docker to Podman easier. + // For now podman buildx build just calls into podman build + // If we are adding new buildx features, we will add them by default + // to podman build. + buildxCmd = &cobra.Command{ + Use: "buildx", + Short: "Build images", + Long: "Build images", + RunE: validate.SubCommandExists, + Hidden: true, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: buildxCmd, + }) +} diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go index a47d01995..176563440 100644 --- a/cmd/podman/images/scp.go +++ b/cmd/podman/images/scp.go @@ -33,7 +33,7 @@ var ( Short: "securely copy images", RunE: scp, Args: cobra.RangeArgs(1, 2), - ValidArgsFunction: common.AutocompleteImages, + ValidArgsFunction: common.AutocompleteScp, Example: `podman image scp myimage:latest otherhost::`, } ) diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index ece7d1f98..2eebd9f86 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -100,6 +100,9 @@ func init() { configmapFlagName := "configmap" flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") _ = kubeCmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault) + + buildFlagName := "build" + flags.BoolVar(&kubeOptions.Build, buildFlagName, false, "Build all images in a YAML (given Containerfiles exist)") } _ = flags.MarkHidden("signature-policy") } diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index abc47164b..bf5b9e350 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -48,6 +48,7 @@ var ( podIDFile string replace bool share string + userns string ) func init() { @@ -72,6 +73,10 @@ func init() { flags.StringVar(&createOptions.CGroupParent, cgroupParentflagName, "", "Set parent cgroup for the pod") _ = createCommand.RegisterFlagCompletionFunc(cgroupParentflagName, completion.AutocompleteDefault) + usernsFlagName := "userns" + flags.StringVar(&userns, usernsFlagName, os.Getenv("PODMAN_USERNS"), "User namespace to use") + _ = createCommand.RegisterFlagCompletionFunc(usernsFlagName, common.AutocompleteUserNamespace) + flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") infraConmonPidfileFlagName := "infra-conmon-pidfile" @@ -178,6 +183,11 @@ func create(cmd *cobra.Command, args []string) error { } } + createOptions.Userns, err = specgen.ParseUserNamespace(userns) + if err != nil { + return err + } + if cmd.Flag("pod-id-file").Changed { podIDFD, err = util.OpenExclusiveFile(podIDFile) if err != nil && os.IsExist(err) { diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go index b512ba341..50e488b02 100644 --- a/cmd/podman/registry/config.go +++ b/cmd/podman/registry/config.go @@ -89,7 +89,12 @@ func newPodmanConfig() { // use for the containers.conf configuration file. func setXdgDirs() error { if !rootless.IsRootless() { - return nil + // unset XDG_RUNTIME_DIR for root + // Sometimes XDG_RUNTIME_DIR is set to /run/user/0 sometimes it is unset, + // the inconsistency is causing issues for the dnsname plugin. + // It is already set to an empty string for conmon so lets do the same + // for podman. see #10806 and #10745 + return os.Unsetenv("XDG_RUNTIME_DIR") } // Setup XDG_RUNTIME_DIR diff --git a/cmd/podman/root.go b/cmd/podman/root.go index dc4ebb952..371ded9a8 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -6,6 +6,7 @@ import ( "path/filepath" "runtime" "runtime/pprof" + "strconv" "strings" "github.com/containers/common/pkg/completion" @@ -194,6 +195,17 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { return err } } + if cmd.Flag("memory-profile").Changed { + // Same value as the default in github.com/pkg/profile. + runtime.MemProfileRate = 4096 + if rate := os.Getenv("MemProfileRate"); rate != "" { + r, err := strconv.Atoi(rate) + if err != nil { + return err + } + runtime.MemProfileRate = r + } + } if cfg.MaxWorks <= 0 { return errors.Errorf("maximum workers must be set to a positive number (got %d)", cfg.MaxWorks) @@ -224,14 +236,29 @@ func persistentPostRunE(cmd *cobra.Command, args []string) error { return nil } - if !registry.IsRemote() { - if cmd.Flag("cpu-profile").Changed { - pprof.StopCPUProfile() + registry.ImageEngine().Shutdown(registry.Context()) + registry.ContainerEngine().Shutdown(registry.Context()) + + if registry.IsRemote() { + return nil + } + + // CPU and memory profiling. + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + if cmd.Flag("memory-profile").Changed { + f, err := os.Create(registry.PodmanConfig().MemoryProfile) + if err != nil { + return errors.Wrap(err, "creating memory profile") + } + defer f.Close() + runtime.GC() // get up-to-date GC statistics + if err := pprof.WriteHeapProfile(f); err != nil { + return errors.Wrap(err, "writing memory profile") } } - registry.ImageEngine().Shutdown(registry.Context()) - registry.ContainerEngine().Shutdown(registry.Context()) return nil } @@ -294,7 +321,8 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { pFlags.StringVar(&cfg.Engine.CgroupManager, cgroupManagerFlagName, cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") _ = cmd.RegisterFlagCompletionFunc(cgroupManagerFlagName, common.AutocompleteCgroupManager) - pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu profiling results") + pFlags.StringVar(&opts.CPUProfile, "cpu-profile", "", "Path for the cpu-profiling results") + pFlags.StringVar(&opts.MemoryProfile, "memory-profile", "", "Path for the memory-profiling results") conmonFlagName := "conmon" pFlags.StringVar(&opts.ConmonPath, conmonFlagName, "", "Path of the conmon binary") @@ -354,6 +382,7 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { "cpu-profile", "default-mounts-file", "max-workers", + "memory-profile", "registries-conf", "trace", } { diff --git a/cmd/podman/shell_completion_test.go b/cmd/podman/shell_completion_test.go index 9bd821d8d..792beeb19 100644 --- a/cmd/podman/shell_completion_test.go +++ b/cmd/podman/shell_completion_test.go @@ -33,7 +33,9 @@ func TestShellCompletionFunctions(t *testing.T) { func checkCommand(t *testing.T, cmd *cobra.Command) { if cmd.HasSubCommands() { for _, childCmd := range cmd.Commands() { - checkCommand(t, childCmd) + if !childCmd.Hidden { + checkCommand(t, childCmd) + } } // if not check if completion for that command is provided diff --git a/cmd/podman/volumes/export.go b/cmd/podman/volumes/export.go new file mode 100644 index 000000000..9e4fecdfa --- /dev/null +++ b/cmd/podman/volumes/export.go @@ -0,0 +1,96 @@ +package volumes + +import ( + "context" + "fmt" + + "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" + "github.com/containers/podman/v3/cmd/podman/inspect" + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/utils" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + volumeExportDescription = ` +podman volume export + +Allow content of volume to be exported into external tar.` + exportCommand = &cobra.Command{ + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Use: "export [options] VOLUME", + Short: "Export volumes", + Args: cobra.ExactArgs(1), + Long: volumeExportDescription, + RunE: export, + ValidArgsFunction: common.AutocompleteVolumes, + } +) + +var ( + // Temporary struct to hold cli values. + cliExportOpts = struct { + Output string + }{} +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: exportCommand, + Parent: volumeCmd, + }) + flags := exportCommand.Flags() + + outputFlagName := "output" + flags.StringVarP(&cliExportOpts.Output, outputFlagName, "o", "/dev/stdout", "Write to a specified file (default: stdout, which must be redirected)") + _ = exportCommand.RegisterFlagCompletionFunc(outputFlagName, completion.AutocompleteDefault) +} + +func export(cmd *cobra.Command, args []string) error { + var inspectOpts entities.InspectOptions + containerEngine := registry.ContainerEngine() + ctx := context.Background() + + if cliExportOpts.Output == "" { + return errors.New("expects output path, use --output=[path]") + } + inspectOpts.Type = inspect.VolumeType + volumeData, _, err := containerEngine.VolumeInspect(ctx, args, inspectOpts) + if err != nil { + return err + } + if len(volumeData) < 1 { + return errors.New("no volume data found") + } + mountPoint := volumeData[0].VolumeConfigResponse.Mountpoint + driver := volumeData[0].VolumeConfigResponse.Driver + volumeOptions := volumeData[0].VolumeConfigResponse.Options + volumeMountStatus, err := containerEngine.VolumeMounted(ctx, args[0]) + if err != nil { + return err + } + if mountPoint == "" { + return errors.New("volume is not mounted anywhere on host") + } + // Check if volume is using external plugin and export only if volume is mounted + if driver != "" && driver != "local" { + if !volumeMountStatus.Value { + return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint) + } + } + // Check if volume is using `local` driver and has mount options type other than tmpfs + if driver == "local" { + if mountOptionType, ok := volumeOptions["type"]; ok { + if mountOptionType != "tmpfs" && !volumeMountStatus.Value { + return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint) + } + } + } + logrus.Debugf("Exporting volume data from %s to %s", mountPoint, cliExportOpts.Output) + err = utils.CreateTarFromSrc(mountPoint, cliExportOpts.Output) + return err +} diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go new file mode 100644 index 000000000..441bd0fe4 --- /dev/null +++ b/cmd/podman/volumes/import.go @@ -0,0 +1,97 @@ +package volumes + +import ( + "fmt" + "os" + + "github.com/containers/podman/v3/cmd/podman/common" + "github.com/containers/podman/v3/cmd/podman/inspect" + "github.com/containers/podman/v3/cmd/podman/parse" + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + importDescription = `Imports contents into a podman volume from specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).` + importCommand = &cobra.Command{ + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Use: "import VOLUME [SOURCE]", + Short: "Import a tarball contents into a podman volume", + Long: importDescription, + RunE: importVol, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteVolumes, + Example: `podman volume import my_vol /home/user/import.tar + cat ctr.tar | podman import volume my_vol -`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: importCommand, + Parent: volumeCmd, + }) +} + +func importVol(cmd *cobra.Command, args []string) error { + var inspectOpts entities.InspectOptions + var tarFile *os.File + containerEngine := registry.ContainerEngine() + ctx := registry.Context() + // create a slice of volumes since inspect expects slice as arg + volumes := []string{args[0]} + tarPath := args[1] + + if tarPath != "-" { + err := parse.ValidateFileName(tarPath) + if err != nil { + return err + } + + // open tar file + tarFile, err = os.Open(tarPath) + if err != nil { + return err + } + } else { + tarFile = os.Stdin + } + + inspectOpts.Type = inspect.VolumeType + volumeData, _, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) + if err != nil { + return err + } + if len(volumeData) < 1 { + return errors.New("no volume data found") + } + mountPoint := volumeData[0].VolumeConfigResponse.Mountpoint + driver := volumeData[0].VolumeConfigResponse.Driver + volumeOptions := volumeData[0].VolumeConfigResponse.Options + volumeMountStatus, err := containerEngine.VolumeMounted(ctx, args[0]) + if err != nil { + return err + } + if mountPoint == "" { + return errors.New("volume is not mounted anywhere on host") + } + // Check if volume is using external plugin and export only if volume is mounted + if driver != "" && driver != "local" { + if !volumeMountStatus.Value { + return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint) + } + } + // Check if volume is using `local` driver and has mount options type other than tmpfs + if driver == "local" { + if mountOptionType, ok := volumeOptions["type"]; ok { + if mountOptionType != "tmpfs" && !volumeMountStatus.Value { + return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint) + } + } + } + // dont care if volume is mounted or not we are gonna import everything to mountPoint + return utils.UntarToFileSystem(mountPoint, tarFile, nil) +} |