diff options
Diffstat (limited to 'cmd')
25 files changed, 506 insertions, 105 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index f792b2713..83fe0723c 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -313,6 +313,10 @@ func completeKeyValues(toComplete string, k keyValueCompletion) ([]string, cobra return suggestions, directive } +func getBoolCompletion(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp +} + /* Autocomplete Functions for cobra ValidArgsFunction */ // AutocompleteContainers - Autocomplete all container names. @@ -797,6 +801,39 @@ func AutocompleteVolumeFlag(cmd *cobra.Command, args []string, toComplete string return volumes, directive } +// AutocompleteNetworkFlag - Autocomplete network flag options. +func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "container:": func(s string) ([]string, cobra.ShellCompDirective) { return getContainers(cmd, s, completeDefault) }, + "ns:": func(_ string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveDefault + }, + "bridge": nil, + "none": nil, + "host": nil, + "private": nil, + "slirp4netns:": func(s string) ([]string, cobra.ShellCompDirective) { + skv := keyValueCompletion{ + "allow_host_loopback=": getBoolCompletion, + "cidr=": nil, + "enable_ipv6=": getBoolCompletion, + "outbound_addr=": nil, + "outbound_addr6=": nil, + "port_handler=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"rootlesskit", "slirp4netns"}, cobra.ShellCompDirectiveNoFileComp + }, + } + return completeKeyValues(s, skv) + }, + } + + networks, _ := getNetworks(cmd, toComplete) + suggestions, dir := completeKeyValues(toComplete, kv) + // add slirp4netns here it does not work correct if we add it to the kv map + suggestions = append(suggestions, "slirp4netns") + return append(networks, suggestions...), dir +} + // AutocompleteJSONFormat - Autocomplete format flag option. // -> "json" func AutocompleteJSONFormat(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -974,17 +1011,14 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri // AutocompleteImageFilters - Autocomplete image ls --filter options. func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - getBool := func(_ string) ([]string, cobra.ShellCompDirective) { - return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp - } getImg := func(s string) ([]string, cobra.ShellCompDirective) { return getImages(cmd, s) } kv := keyValueCompletion{ "before=": getImg, "since=": getImg, "label=": nil, "reference=": nil, - "dangling=": getBool, - "readonly=": getBool, + "dangling=": getBoolCompletion, + "readonly=": getBoolCompletion, } return completeKeyValues(toComplete, kv) } @@ -1004,14 +1038,12 @@ func AutocompleteVolumeFilters(cmd *cobra.Command, args []string, toComplete str return []string{"local"}, cobra.ShellCompDirectiveNoFileComp } kv := keyValueCompletion{ - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, - "driver=": local, - "scope=": local, - "label=": nil, - "opt=": nil, - "dangling=": func(_ string) ([]string, cobra.ShellCompDirective) { - return []string{"true", "false"}, cobra.ShellCompDirectiveNoFileComp - }, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getVolumes(cmd, s) }, + "driver=": local, + "scope=": local, + "label=": nil, + "opt=": nil, + "dangling=": getBoolCompletion, } return completeKeyValues(toComplete, kv) } diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 14086ace4..bbd4f6bae 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -642,7 +642,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { storageOptFlagName := "storage-opt" createFlags.StringSliceVar( - &cf.StoreageOpt, + &cf.StorageOpt, storageOptFlagName, []string{}, "Storage driver options per container", ) @@ -671,7 +671,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { sysctlFlagName, []string{}, "Sysctl options", ) - //TODO: Add function for systctl completion. + //TODO: Add function for sysctl completion. _ = cmd.RegisterFlagCompletionFunc(sysctlFlagName, completion.AutocompleteNone) systemdFlagName := "systemd" @@ -696,13 +696,13 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) { "Allocate a pseudo-TTY for container", ) - timezonezFlagName := "tz" + timezoneFlagName := "tz" createFlags.StringVar( &cf.Timezone, - timezonezFlagName, containerConfig.TZ(), + timezoneFlagName, containerConfig.TZ(), "Set timezone in container", ) - _ = cmd.RegisterFlagCompletionFunc(timezonezFlagName, completion.AutocompleteNone) //TODO: add timezone completion + _ = cmd.RegisterFlagCompletionFunc(timezoneFlagName, completion.AutocompleteNone) //TODO: add timezone completion umaskFlagName := "umask" createFlags.StringVar( diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index e975def0a..9635f4135 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -98,7 +98,7 @@ type ContainerCLIOpts struct { SignaturePolicy string StopSignal string StopTimeout uint - StoreageOpt []string + StorageOpt []string SubUIDName string SubGIDName string Sysctl []string @@ -310,7 +310,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup // on speculation by Matt and I. We think that these come into play later // like with start. We believe this is just a difference in podman/compat cliOpts := ContainerCLIOpts{ - // Attach: nil, // dont need? + // Attach: nil, // don't need? Authfile: "", CapAdd: append(capAdd, cc.HostConfig.CapAdd...), CapDrop: append(cappDrop, cc.HostConfig.CapDrop...), @@ -321,11 +321,11 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup CPURTPeriod: uint64(cc.HostConfig.CPURealtimePeriod), CPURTRuntime: cc.HostConfig.CPURealtimeRuntime, CPUShares: uint64(cc.HostConfig.CPUShares), - // CPUS: 0, // dont need? + // CPUS: 0, // don't need? CPUSetCPUs: cc.HostConfig.CpusetCpus, CPUSetMems: cc.HostConfig.CpusetMems, - // Detach: false, // dont need - // DetachKeys: "", // dont need + // Detach: false, // don't need + // DetachKeys: "", // don't need Devices: devices, DeviceCGroupRule: nil, DeviceReadBPs: readBps, @@ -359,7 +359,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, cgroup Rm: cc.HostConfig.AutoRemove, SecurityOpt: cc.HostConfig.SecurityOpt, StopSignal: cc.Config.StopSignal, - StoreageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), + StorageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), Systemd: "true", // podman default TmpFS: stringMaptoArray(cc.HostConfig.Tmpfs), diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go index cae52ccaa..9cb4ed550 100644 --- a/cmd/podman/common/netflags.go +++ b/cmd/podman/common/netflags.go @@ -63,7 +63,7 @@ func DefineNetFlags(cmd *cobra.Command) { networkFlagName, containerConfig.NetNS(), "Connect a container to a network", ) - _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworks) + _ = cmd.RegisterFlagCompletionFunc(networkFlagName, AutocompleteNetworkFlag) networkAliasFlagName := "network-alias" netFlags.StringSlice( diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index c416d0d7b..287836d9f 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -488,9 +488,9 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.ConmonPidFile = c.ConmonPIDFile // TODO - // ouitside of specgen and oci though + // outside of specgen and oci though // defaults to true, check spec/storage - // s.readon = c.ReadOnlyTmpFS + // s.readonly = c.ReadOnlyTmpFS // TODO convert to map? // check if key=value and convert sysmap := make(map[string]string) diff --git a/cmd/podman/completion/completion.go b/cmd/podman/completion/completion.go index 84942a508..b615b33d0 100644 --- a/cmd/podman/completion/completion.go +++ b/cmd/podman/completion/completion.go @@ -32,7 +32,7 @@ var ( Example: `podman completion bash podman completion zsh -f _podman podman completion fish --no-desc`, - //dont show this command to users + //don't show this command to users Hidden: true, } ) diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index 9b0a01a2f..69b61a06c 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -1,19 +1,34 @@ package containers import ( + "io" + "io/ioutil" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + + buildahCopiah "github.com/containers/buildah/copier" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/pkg/copy" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/errorhandling" + "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/idtools" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( - cpDescription = `Command copies the contents of SRC_PATH to the DEST_PATH. + cpDescription = `Copy the contents of SRC_PATH to the DEST_PATH. - You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. + You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or a directory. ` cpCommand = &cobra.Command{ - Use: "cp [options] [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", + Use: "cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", Short: "Copy files/folders between a container and the local filesystem", Long: cpDescription, Args: cobra.ExactArgs(2), @@ -39,19 +54,21 @@ var ( func cpFlags(cmd *cobra.Command) { flags := cmd.Flags() - flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.") - flags.BoolVar(&cpOpts.Pause, "pause", true, "Pause the container while copying") + flags.BoolVar(&cpOpts.Extract, "extract", false, "Deprecated...") + flags.BoolVar(&cpOpts.Pause, "pause", true, "Deprecated") + _ = flags.MarkHidden("extract") + _ = flags.MarkHidden("pause") } func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: cpCommand, }) cpFlags(cpCommand) registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: containerCpCommand, Parent: containerCmd, }) @@ -59,5 +76,290 @@ func init() { } func cp(cmd *cobra.Command, args []string) error { - return registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts) + // Parse user input. + sourceContainerStr, sourcePath, destContainerStr, destPath, err := copy.ParseSourceAndDestination(args[0], args[1]) + if err != nil { + return err + } + + if len(sourceContainerStr) > 0 { + return copyFromContainer(sourceContainerStr, sourcePath, destPath) + } + + return copyToContainer(destContainerStr, destPath, sourcePath) +} + +// containerMustExist returns an error if the specified container does not +// exist. +func containerMustExist(container string) error { + exists, err := registry.ContainerEngine().ContainerExists(registry.GetContext(), container, entities.ContainerExistsOptions{}) + if err != nil { + return err + } + if !exists.Value { + return errors.Errorf("container %q does not exist", container) + } + return nil +} + +// doCopy executes the two functions in parallel to copy data from A to B and +// joins the errors if any. +func doCopy(funcA func() error, funcB func() error) error { + errChan := make(chan error) + go func() { + errChan <- funcA() + }() + var copyErrors []error + copyErrors = append(copyErrors, funcB()) + copyErrors = append(copyErrors, <-errChan) + return errorhandling.JoinErrors(copyErrors) +} + +// copyFromContainer copies from the containerPath on the container to hostPath. +func copyFromContainer(container string, containerPath string, hostPath string) error { + if err := containerMustExist(container); err != nil { + return err + } + + if hostPath == "-" { + hostPath = os.Stdout.Name() + } + + containerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath) + if err != nil { + return errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + } + + var hostBaseName string + hostInfo, hostInfoErr := copy.ResolveHostPath(hostPath) + if hostInfoErr != nil { + if strings.HasSuffix(hostPath, "/") { + return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath) + } + // If it doesn't exist, then let's have a look at the parent dir. + parentDir := filepath.Dir(hostPath) + hostInfo, err = copy.ResolveHostPath(parentDir) + if err != nil { + return errors.Wrapf(hostInfoErr, "%q could not be found on the host", hostPath) + } + // If the specified path does not exist, we need to assume that + // it'll be created while copying. Hence, we use it as the + // base path. + hostBaseName = filepath.Base(hostPath) + } else { + // If the specified path exists on the host, we must use its + // base path as it may have changed due to symlink evaluations. + hostBaseName = filepath.Base(hostInfo.LinkTarget) + } + + reader, writer := io.Pipe() + hostCopy := func() error { + defer reader.Close() + if hostInfo.LinkTarget == os.Stdout.Name() { + _, err := io.Copy(os.Stdout, reader) + return err + } + + groot, err := user.Current() + if err != nil { + return err + } + + // Set the {G,U}ID. Let's be tolerant towards the different + // operating systems and only log the errors, so we can debug + // if necessary. + idPair := idtools.IDPair{} + if i, err := strconv.Atoi(groot.Uid); err == nil { + idPair.UID = i + } else { + logrus.Debugf("Error converting UID %q to int: %v", groot.Uid, err) + } + if i, err := strconv.Atoi(groot.Gid); err == nil { + idPair.GID = i + } else { + logrus.Debugf("Error converting GID %q to int: %v", groot.Gid, err) + } + + putOptions := buildahCopiah.PutOptions{ + ChownDirs: &idPair, + ChownFiles: &idPair, + } + if !containerInfo.IsDir && (!hostInfo.IsDir || hostInfoErr != nil) { + // If we're having a file-to-file copy, make sure to + // rename accordingly. + putOptions.Rename = map[string]string{filepath.Base(containerInfo.LinkTarget): hostBaseName} + } + dir := hostInfo.LinkTarget + if !hostInfo.IsDir { + dir = filepath.Dir(dir) + } + if err := buildahCopiah.Put(dir, "", putOptions, reader); err != nil { + return errors.Wrap(err, "error copying to host") + } + return nil + } + + containerCopy := func() error { + defer writer.Close() + copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), container, containerInfo.LinkTarget, writer) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying from container") + } + return nil + } + return doCopy(containerCopy, hostCopy) +} + +// copyToContainer copies the hostPath to containerPath on the container. +func copyToContainer(container string, containerPath string, hostPath string) error { + if err := containerMustExist(container); err != nil { + return err + } + + isStdin := false + if hostPath == "-" { + hostPath = os.Stdin.Name() + isStdin = true + } else if hostPath == os.Stdin.Name() { + isStdin = true + } + + // Make sure that host path exists. + hostInfo, err := copy.ResolveHostPath(hostPath) + if err != nil { + return errors.Wrapf(err, "%q could not be found on the host", hostPath) + } + + // If the path on the container does not exist. We need to make sure + // that it's parent directory exists. The destination may be created + // while copying. + var containerBaseName string + containerInfo, containerInfoErr := registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath) + if containerInfoErr != nil { + if strings.HasSuffix(containerPath, "/") { + return errors.Wrapf(containerInfoErr, "%q could not be found on container %s", containerPath, container) + } + if isStdin { + return errors.New("destination must be a directory when copying from stdin") + } + // NOTE: containerInfo may actually be set. That happens when + // the container path is a symlink into nirvana. In that case, + // we must use the symlinked path instead. + path := containerPath + if containerInfo != nil { + containerBaseName = filepath.Base(containerInfo.LinkTarget) + path = containerInfo.LinkTarget + } else { + containerBaseName = filepath.Base(containerPath) + } + + parentDir, err := containerParentDir(container, path) + if err != nil { + return errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, container) + } + containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, parentDir) + if err != nil { + return errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + } + } else { + // If the specified path exists on the container, we must use + // its base path as it may have changed due to symlink + // evaluations. + containerBaseName = filepath.Base(containerInfo.LinkTarget) + } + + var stdinFile string + if isStdin { + if !containerInfo.IsDir { + return errors.New("destination must be a directory when copying from stdin") + } + + // Copy from stdin to a temporary file *before* throwing it + // over the wire. This allows for proper client-side error + // reporting. + tmpFile, err := ioutil.TempFile("", "") + if err != nil { + return err + } + _, err = io.Copy(tmpFile, os.Stdin) + if err != nil { + return err + } + if err = tmpFile.Close(); err != nil { + return err + } + if !archive.IsArchivePath(tmpFile.Name()) { + return errors.New("source must be a (compressed) tar archive when copying from stdin") + } + stdinFile = tmpFile.Name() + } + + reader, writer := io.Pipe() + hostCopy := func() error { + defer writer.Close() + if isStdin { + stream, err := os.Open(stdinFile) + if err != nil { + return err + } + defer stream.Close() + _, err = io.Copy(writer, stream) + return err + } + + getOptions := buildahCopiah.GetOptions{ + // Unless the specified path ends with ".", we want to copy the base directory. + KeepDirectoryNames: !strings.HasSuffix(hostPath, "."), + } + if !hostInfo.IsDir && (!containerInfo.IsDir || containerInfoErr != nil) { + // If we're having a file-to-file copy, make sure to + // rename accordingly. + getOptions.Rename = map[string]string{filepath.Base(hostInfo.LinkTarget): containerBaseName} + } + if err := buildahCopiah.Get("/", "", getOptions, []string{hostInfo.LinkTarget}, writer); err != nil { + return errors.Wrap(err, "error copying from host") + } + return nil + } + + containerCopy := func() error { + defer reader.Close() + target := containerInfo.FileInfo.LinkTarget + if !containerInfo.IsDir { + target = filepath.Dir(target) + } + + copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), container, target, reader) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying to container") + } + return nil + } + + return doCopy(hostCopy, containerCopy) +} + +// containerParentDir returns the parent directory of the specified path on the +// container. If the path is relative, it will be resolved relative to the +// container's working directory (or "/" if the work dir isn't set). +func containerParentDir(container string, containerPath string) (string, error) { + if filepath.IsAbs(containerPath) { + return filepath.Dir(containerPath), nil + } + inspectData, _, err := registry.ContainerEngine().ContainerInspect(registry.GetContext(), []string{container}, entities.InspectOptions{}) + if err != nil { + return "", err + } + if len(inspectData) != 1 { + return "", errors.Errorf("inspecting container %q: expected 1 data item but got %d", container, len(inspectData)) + } + workDir := filepath.Join("/", inspectData[0].Config.WorkingDir) + workDir = filepath.Join(workDir, containerPath) + return filepath.Dir(workDir), nil } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 3d87c71a9..420813ba9 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -171,7 +171,7 @@ func createInit(c *cobra.Command) error { } cliVals.UserNS = c.Flag("userns").Value.String() // if user did not modify --userns flag and did turn on - // uid/gid mappsings, set userns flag to "private" + // uid/gid mappings, set userns flag to "private" if !c.Flag("userns").Changed && cliVals.UserNS == "host" { if len(cliVals.UIDMap) > 0 || len(cliVals.GIDMap) > 0 || @@ -239,7 +239,7 @@ func pullImage(imageName string) (string, error) { if cliVals.Platform != "" { if cliVals.OverrideArch != "" || cliVals.OverrideOS != "" { - return "", errors.Errorf("--platform option can not be specified with --overide-arch or --override-os") + return "", errors.Errorf("--platform option can not be specified with --override-arch or --override-os") } split := strings.SplitN(cliVals.Platform, "/", 2) cliVals.OverrideOS = split[0] diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 4640229a9..28040e08a 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" @@ -22,7 +23,7 @@ var ( Long: killDescription, RunE: kill, Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, ValidArgsFunction: common.AutocompleteContainersRunning, Example: `podman kill mywebserver @@ -32,7 +33,7 @@ var ( containerKillCommand = &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { - return validate.CheckAllLatestAndCIDFile(cmd, args, false, false) + return validate.CheckAllLatestAndCIDFile(cmd, args, false, true) }, Use: killCommand.Use, Short: killCommand.Short, @@ -57,6 +58,9 @@ func killFlags(cmd *cobra.Command) { signalFlagName := "signal" flags.StringVarP(&killOptions.Signal, signalFlagName, "s", "KILL", "Signal to send to the container") _ = cmd.RegisterFlagCompletionFunc(signalFlagName, common.AutocompleteStopSignal) + cidfileFlagName := "cidfile" + flags.StringArrayVar(&killOptions.CIDFiles, cidfileFlagName, []string{}, "Read the container ID from the file") + _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) } func init() { diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index d4ede370a..9b562afd8 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -122,6 +122,7 @@ func logs(_ *cobra.Command, args []string) error { } logsOptions.Since = since } - logsOptions.Writer = os.Stdout + logsOptions.StdoutWriter = os.Stdout + logsOptions.StderrWriter = os.Stderr return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions) } diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go index fb2101d64..c92b19a19 100644 --- a/cmd/podman/containers/mount.go +++ b/cmd/podman/containers/mount.go @@ -39,7 +39,7 @@ var ( ValidArgsFunction: common.AutocompleteContainers, } - containerMountCommmand = &cobra.Command{ + containerMountCommand = &cobra.Command{ Use: mountCommand.Use, Short: mountCommand.Short, Long: mountCommand.Long, @@ -76,11 +76,11 @@ func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, - Command: containerMountCommmand, + Command: containerMountCommand, Parent: containerCmd, }) - mountFlags(containerMountCommmand) - validate.AddLatestFlag(containerMountCommmand, &mountOpts.Latest) + mountFlags(containerMountCommand) + validate.AddLatestFlag(containerMountCommand, &mountOpts.Latest) } func mount(_ *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index 6ff1b929d..46bfb4143 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -3,7 +3,6 @@ package containers import ( "fmt" "os" - "strconv" "strings" "github.com/containers/common/pkg/completion" @@ -15,7 +14,6 @@ import ( "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/rootless" "github.com/containers/podman/v2/pkg/specgen" - "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -108,15 +106,6 @@ func run(cmd *cobra.Command, args []string) error { return err } - if rootless.IsRootless() && !registry.IsRemote() { - userspec := strings.SplitN(cliVals.User, ":", 2)[0] - if uid, err := strconv.ParseInt(userspec, 10, 32); err == nil { - if err := util.CheckRootlessUIDRange(int(uid)); err != nil { - return err - } - } - } - if af := cliVals.Authfile; len(af) > 0 { if _, err := os.Stat(af); err != nil { return err diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index fbea1e3d8..3aca104e3 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -221,7 +221,8 @@ func build(cmd *cobra.Command, args []string) error { var logfile *os.File if cmd.Flag("logfile").Changed { - logfile, err := os.OpenFile(buildOpts.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + var err error + logfile, err = os.OpenFile(buildOpts.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return err } diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index f8e1ee226..2d881a906 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -139,7 +139,7 @@ func imagePull(cmd *cobra.Command, args []string) error { } if platform != "" { if pullOptions.OverrideArch != "" || pullOptions.OverrideOS != "" { - return errors.Errorf("--platform option can not be specified with --overide-arch or --override-os") + return errors.Errorf("--platform option can not be specified with --override-arch or --override-os") } split := strings.SplitN(platform, "/", 2) pullOptions.OverrideOS = split[0] diff --git a/cmd/podman/images/sign.go b/cmd/podman/images/sign.go index 342536f7c..859d51d51 100644 --- a/cmd/podman/images/sign.go +++ b/cmd/podman/images/sign.go @@ -47,6 +47,7 @@ func init() { certDirFlagName := "cert-dir" flags.StringVar(&signOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") _ = signCommand.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + flags.BoolVarP(&signOptions.All, "all", "a", false, "Sign all the manifests of the multi-architecture image") } func sign(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index cc48b7ae4..76613ae71 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -51,7 +51,7 @@ func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { _ = cmd.RegisterFlagCompletionFunc(formatFlagName, completion.AutocompleteNone) typeFlagName := "type" - flags.StringVarP(&opts.Type, typeFlagName, "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) + flags.StringVarP(&opts.Type, typeFlagName, "t", AllType, fmt.Sprintf("Specify inspect-object type (%q, %q or %q)", ImageType, ContainerType, AllType)) _ = cmd.RegisterFlagCompletionFunc(typeFlagName, common.AutocompleteInspectType) validate.AddLatestFlag(cmd, &opts.Latest) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index c3aaf84a8..f076d13f3 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -31,11 +31,6 @@ func main() { return } - // Hard code TMPDIR functions to use /var/tmp, if user did not override - if _, ok := os.LookupEnv("TMPDIR"); !ok { - os.Setenv("TMPDIR", "/var/tmp") - } - rootCmd = parseCommands() Execute() diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index 89faa42a2..96fea4a21 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -107,7 +107,7 @@ func push(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("tls-verify") { manifestPushOpts.SkipTLSVerify = types.NewOptionalBool(!manifestPushOpts.TLSVerifyCLI) } - if err := registry.ImageEngine().ManifestPush(registry.Context(), args, manifestPushOpts.ManifestPushOptions); err != nil { + if err := registry.ImageEngine().ManifestPush(registry.Context(), args[0], args[1], manifestPushOpts.ManifestPushOptions); err != nil { return err } return nil diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index 5e227d05a..db7280b1d 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -61,7 +61,7 @@ func init() { networkFlagName := "network" flags.StringVar(&kubeOptions.Network, networkFlagName, "", "Connect pod to CNI network(s)") - _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworks) + _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) logDriverFlagName := "log-driver" flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 0830a62a5..1f613a4c5 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -178,6 +178,10 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { return err } } + // Hard code TMPDIR functions to use /var/tmp, if user did not override + if _, ok := os.LookupEnv("TMPDIR"); !ok { + os.Setenv("TMPDIR", "/var/tmp") + } if !registry.IsRemote() { if cmd.Flag("cpu-profile").Changed { diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 57e747451..da5f652c8 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -168,19 +168,17 @@ func getUserInfo(uri *url.URL) (*url.Userinfo, error) { } func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { - var authMethods []ssh.AuthMethod - passwd, set := uri.User.Password() - if set { - authMethods = append(authMethods, ssh.Password(passwd)) - } + var signers []ssh.Signer + passwd, passwdSet := uri.User.Password() if cmd.Flags().Changed("identity") { value := cmd.Flag("identity").Value.String() - auth, err := terminal.PublicKey(value, []byte(passwd)) + s, err := terminal.PublicKey(value, []byte(passwd)) if err != nil { return "", errors.Wrapf(err, "failed to read identity %q", value) } - authMethods = append(authMethods, auth) + signers = append(signers, s) + logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) } if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { @@ -190,16 +188,51 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { if err != nil { return "", err } - a := agent.NewClient(c) - authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) - } - - if len(authMethods) == 0 { - pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) + agentSigners, err := agent.NewClient(c).Signers() if err != nil { return "", err } - authMethods = append(authMethods, ssh.Password(string(pass))) + + signers = append(signers, agentSigners...) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + for _, s := range agentSigners { + logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + } + } + + var authMethods []ssh.AuthMethod + if len(signers) > 0 { + var dedup = make(map[string]ssh.Signer) + // Dedup signers based on fingerprint, ssh-agent keys override CONTAINER_SSHKEY + for _, s := range signers { + fp := ssh.FingerprintSHA256(s.PublicKey()) + if _, found := dedup[fp]; found { + logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + dedup[fp] = s + } + + var uniq []ssh.Signer + for _, s := range dedup { + uniq = append(uniq, s) + } + + authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { + return uniq, nil + })) + } + + if passwdSet { + authMethods = append(authMethods, ssh.Password(passwd)) + } + + if len(authMethods) == 0 { + authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { + pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) + return string(pass), err + })) } cfg := &ssh.ClientConfig{ diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index f57689584..87bb947ed 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -12,12 +12,14 @@ import ( "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" + dfilters "github.com/containers/podman/v2/pkg/domain/filters" + "github.com/docker/go-units" "github.com/spf13/cobra" ) var ( - pruneOptions = entities.SystemPruneOptions{} - + pruneOptions = entities.SystemPruneOptions{} + filters []string pruneDescription = fmt.Sprintf(` podman system prune @@ -46,10 +48,15 @@ func init() { flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") flags.BoolVarP(&pruneOptions.All, "all", "a", false, "Remove all unused data") flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes") + filterFlagName := "filter" + flags.StringArrayVar(&filters, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) } func prune(cmd *cobra.Command, args []string) error { + var err error + // Prompt for confirmation if --force is not set if !force { reader := bufio.NewReader(os.Stdin) @@ -74,13 +81,17 @@ Are you sure you want to continue? [y/N] `, volumeString) } } - // TODO: support for filters in system prune + pruneOptions.Filters, err = dfilters.ParseFilterArgumentsIntoFilters(filters) + if err != nil { + return err + } + response, err := registry.ContainerEngine().SystemPrune(context.Background(), pruneOptions) if err != nil { return err } // Print container prune results - err = utils.PrintContainerPruneResults(response.ContainerPruneReport, true) + err = utils.PrintContainerPruneResults(response.ContainerPruneReports, true) if err != nil { return err } @@ -91,13 +102,17 @@ Are you sure you want to continue? [y/N] `, volumeString) } // Print Volume prune results if pruneOptions.Volume { - err = utils.PrintVolumePruneResults(response.VolumePruneReport, true) + err = utils.PrintVolumePruneResults(response.VolumePruneReports, true) if err != nil { return err } } // Print Images prune results - utils.PrintImagePruneResults(response.ImagePruneReport, true) + err = utils.PrintImagePruneResults(response.ImagePruneReports, true) + if err != nil { + return err + } + fmt.Printf("Total reclaimed space: %s\n", units.HumanSize((float64)(response.ReclaimedSpace))) return nil } diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 8c52616be..ed35fbb04 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -5,6 +5,7 @@ package system import ( "context" "net" + "os" "strings" api "github.com/containers/podman/v2/pkg/api/server" @@ -13,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/pflag" + "golang.org/x/sys/unix" ) func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error { @@ -34,6 +36,15 @@ func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entiti listener = &l } + // Close stdin, so shortnames will not prompt + devNullfile, err := os.Open(os.DevNull) + if err != nil { + return err + } + defer devNullfile.Close() + if err := unix.Dup2(int(devNullfile.Fd()), int(os.Stdin.Fd())); err != nil { + return err + } rt, err := infra.GetRuntime(context.Background(), flags, cfg) if err != nil { return err diff --git a/cmd/podman/utils/utils.go b/cmd/podman/utils/utils.go index 2ca2c4c92..f42243f69 100644 --- a/cmd/podman/utils/utils.go +++ b/cmd/podman/utils/utils.go @@ -5,6 +5,7 @@ import ( "os" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/entities/reports" ) // IsDir returns true if the specified path refers to a directory. @@ -41,21 +42,21 @@ func PrintPodPruneResults(podPruneReports []*entities.PodPruneReport, heading bo return errs.PrintErrors() } -func PrintContainerPruneResults(containerPruneReport *entities.ContainerPruneReport, heading bool) error { +func PrintContainerPruneResults(containerPruneReports []*reports.PruneReport, heading bool) error { var errs OutputErrors - if heading && (len(containerPruneReport.ID) > 0 || len(containerPruneReport.Err) > 0) { + if heading && (len(containerPruneReports) > 0) { fmt.Println("Deleted Containers") } - for k := range containerPruneReport.ID { - fmt.Println(k) - } - for _, v := range containerPruneReport.Err { - errs = append(errs, v) + for _, v := range containerPruneReports { + fmt.Println(v.Id) + if v.Err != nil { + errs = append(errs, v.Err) + } } return errs.PrintErrors() } -func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport, heading bool) error { +func PrintVolumePruneResults(volumePruneReport []*reports.PruneReport, heading bool) error { var errs OutputErrors if heading && len(volumePruneReport) > 0 { fmt.Println("Deleted Volumes") @@ -70,18 +71,16 @@ func PrintVolumePruneResults(volumePruneReport []*entities.VolumePruneReport, he return errs.PrintErrors() } -func PrintImagePruneResults(imagePruneReport *entities.ImagePruneReport, heading bool) error { - if heading && (len(imagePruneReport.Report.Id) > 0 || len(imagePruneReport.Report.Err) > 0) { +func PrintImagePruneResults(imagePruneReports []*reports.PruneReport, heading bool) error { + if heading { fmt.Println("Deleted Images") } - for _, i := range imagePruneReport.Report.Id { - fmt.Println(i) - } - for _, e := range imagePruneReport.Report.Err { - fmt.Fprint(os.Stderr, e.Error()+"\n") - } - if imagePruneReport.Size > 0 { - fmt.Fprintf(os.Stdout, "Size: %d\n", imagePruneReport.Size) + for _, r := range imagePruneReports { + fmt.Println(r.Id) + if r.Err != nil { + fmt.Fprint(os.Stderr, r.Err.Error()+"\n") + } } + return nil } diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go index d1370120b..0f3ba9ef6 100644 --- a/cmd/podman/volumes/prune.go +++ b/cmd/podman/volumes/prune.go @@ -8,10 +8,12 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/filters" "github.com/spf13/cobra" ) @@ -28,6 +30,7 @@ var ( RunE: prune, ValidArgsFunction: completion.AutocompleteNone, } + filter = []string{} ) func init() { @@ -37,10 +40,17 @@ func init() { Parent: volumeCmd, }) flags := pruneCommand.Flags() + + filterFlagName := "filter" + flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteVolumeFilters) flags.BoolP("force", "f", false, "Do not prompt for confirmation") } func prune(cmd *cobra.Command, args []string) error { + var ( + pruneOptions = entities.VolumePruneOptions{} + ) // Prompt for confirmation if --force is not set force, err := cmd.Flags().GetBool("force") if err != nil { @@ -58,7 +68,11 @@ func prune(cmd *cobra.Command, args []string) error { return nil } } - responses, err := registry.ContainerEngine().VolumePrune(context.Background()) + pruneOptions.Filters, err = filters.ParseFilterArgumentsIntoFilters(filter) + if err != nil { + return err + } + responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions) if err != nil { return err } |