diff options
Diffstat (limited to 'cmd/podman')
-rw-r--r-- | cmd/podman/common/completion.go | 4 | ||||
-rw-r--r-- | cmd/podman/common/create_opts.go | 9 | ||||
-rw-r--r-- | cmd/podman/common/specgen.go | 42 | ||||
-rw-r--r-- | cmd/podman/containers/cp.go | 177 | ||||
-rw-r--r-- | cmd/podman/containers/restore.go | 6 | ||||
-rw-r--r-- | cmd/podman/login.go | 1 | ||||
-rw-r--r-- | cmd/podman/logout.go | 1 | ||||
-rw-r--r-- | cmd/podman/registry/config.go | 3 | ||||
-rw-r--r-- | cmd/podman/root.go | 3 | ||||
-rw-r--r-- | cmd/podman/system/migrate.go | 5 |
10 files changed, 192 insertions, 59 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 177d094aa..08b2f6235 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -46,7 +46,9 @@ func setupContainerEngine(cmd *cobra.Command) (entities.ContainerEngine, error) return nil, err } if !registry.IsRemote() && rootless.IsRootless() { - err := containerEngine.SetupRootless(registry.Context(), cmd) + _, noMoveProcess := cmd.Annotations[registry.NoMoveProcess] + + err := containerEngine.SetupRootless(registry.Context(), noMoveProcess) if err != nil { return nil, err } diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 66778f519..42e0efe5d 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -517,7 +517,14 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c cliOpts.OOMKillDisable = *cc.HostConfig.OomKillDisable } if cc.Config.Healthcheck != nil { - cliOpts.HealthCmd = strings.Join(cc.Config.Healthcheck.Test, " ") + finCmd := "" + for _, str := range cc.Config.Healthcheck.Test { + finCmd = finCmd + str + " " + } + if len(finCmd) > 1 { + 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() diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 24b45e479..42f515ace 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -516,7 +516,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string if len(con) != 2 { return fmt.Errorf("invalid --security-opt 1: %q", opt) } - switch con[0] { case "apparmor": s.ContainerSecurityConfig.ApparmorProfile = con[1] @@ -664,25 +663,40 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string) (*manifest.Schema2HealthConfig, error) { + cmdArr := []string{} + isArr := true + err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling + if err != nil { + cmdArr = strings.SplitN(inCmd, " ", 2) // default for compat + isArr = false + } // Every healthcheck requires a command - if len(inCmd) == 0 { + if len(cmdArr) == 0 { return nil, errors.New("Must define a healthcheck command for all healthchecks") } - - // first try to parse option value as JSON array of strings... - cmd := []string{} - - if inCmd == "none" { - cmd = []string{"NONE"} - } else { - err := json.Unmarshal([]byte(inCmd), &cmd) - if err != nil { - // ...otherwise pass it to "/bin/sh -c" inside the container - cmd = []string{"CMD-SHELL", inCmd} + 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 + if isArr && len(cmdArr) > 1 { // an array of consecutive commands + cmdArr = append([]string{"CMD"}, cmdArr...) + } else { // one singular command + if len(cmdArr) == 1 { + concat = cmdArr[0] + } else { + concat = strings.Join(cmdArr[0:], " ") + } + cmdArr = append([]string{"CMD-SHELL"}, concat) } } + + if cmdArr[0] == "none" { // if specified to remove healtcheck + cmdArr = []string{"NONE"} + } + + // healthcheck is by default an array, so we simply pass the user input hc := manifest.Schema2HealthConfig{ - Test: cmd, + Test: cmdArr, } if interval == "disable" { diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index c1f1e27f5..7b5846a35 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -82,7 +82,9 @@ func cp(cmd *cobra.Command, args []string) error { return err } - if len(sourceContainerStr) > 0 { + if len(sourceContainerStr) > 0 && len(destContainerStr) > 0 { + return copyContainerToContainer(sourceContainerStr, sourcePath, destContainerStr, destPath) + } else if len(sourceContainerStr) > 0 { return copyFromContainer(sourceContainerStr, sourcePath, destPath) } @@ -115,6 +117,84 @@ func doCopy(funcA func() error, funcB func() error) error { return errorhandling.JoinErrors(copyErrors) } +func copyContainerToContainer(sourceContainer string, sourcePath string, destContainer string, destPath string) error { + if err := containerMustExist(sourceContainer); err != nil { + return err + } + + if err := containerMustExist(destContainer); err != nil { + return err + } + + sourceContainerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), sourceContainer, sourcePath) + if err != nil { + return errors.Wrapf(err, "%q could not be found on container %s", sourcePath, sourceContainer) + } + + destContainerBaseName, destContainerInfo, destResolvedToParentDir, err := resolvePathOnDestinationContainer(destContainer, destPath, false) + if err != nil { + return err + } + + if sourceContainerInfo.IsDir && !destContainerInfo.IsDir { + return errors.New("destination must be a directory when copying a directory") + } + + sourceContainerTarget := sourceContainerInfo.LinkTarget + destContainerTarget := destContainerInfo.LinkTarget + if !destContainerInfo.IsDir { + destContainerTarget = filepath.Dir(destPath) + } + + // If we copy a directory via the "." notation and the container path + // does not exist, we need to make sure that the destination on the + // container gets created; otherwise the contents of the source + // directory will be written to the destination's parent directory. + // + // Hence, whenever "." is the source and the destination does not + // exist, we copy the source's parent and let the copier package create + // the destination via the Rename option. + if destResolvedToParentDir && sourceContainerInfo.IsDir && strings.HasSuffix(sourcePath, ".") { + sourceContainerTarget = filepath.Dir(sourceContainerTarget) + } + + reader, writer := io.Pipe() + + sourceContainerCopy := func() error { + defer writer.Close() + copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), sourceContainer, sourceContainerTarget, writer) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying from container") + } + return nil + } + + destContainerCopy := func() error { + defer reader.Close() + + copyOptions := entities.CopyOptions{Chown: chown} + if (!sourceContainerInfo.IsDir && !destContainerInfo.IsDir) || destResolvedToParentDir { + // If we're having a file-to-file copy, make sure to + // rename accordingly. + copyOptions.Rename = map[string]string{filepath.Base(sourceContainerTarget): destContainerBaseName} + } + + copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), destContainer, destContainerTarget, reader, copyOptions) + if err != nil { + return err + } + if err := copyFunc(); err != nil { + return errors.Wrap(err, "error copying to container") + } + return nil + } + + return doCopy(sourceContainerCopy, destContainerCopy) +} + // 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 { @@ -133,6 +213,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) } var hostBaseName string + var resolvedToHostParentDir bool hostInfo, hostInfoErr := copy.ResolveHostPath(hostPath) if hostInfoErr != nil { if strings.HasSuffix(hostPath, "/") { @@ -148,6 +229,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) // it'll be created while copying. Hence, we use it as the // base path. hostBaseName = filepath.Base(hostPath) + resolvedToHostParentDir = true } else { // If the specified path exists on the host, we must use its // base path as it may have changed due to symlink evaluations. @@ -175,7 +257,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) // we copy the source's parent and let the copier package create the // destination via the Rename option. containerTarget := containerInfo.LinkTarget - if hostInfoErr != nil && containerInfo.IsDir && strings.HasSuffix(containerTarget, ".") { + if resolvedToHostParentDir && containerInfo.IsDir && strings.HasSuffix(containerTarget, ".") { containerTarget = filepath.Dir(containerTarget) } @@ -216,7 +298,7 @@ func copyFromContainer(container string, containerPath string, hostPath string) ChownFiles: &idPair, IgnoreDevices: true, } - if (!containerInfo.IsDir && !hostInfo.IsDir) || hostInfoErr != nil { + if (!containerInfo.IsDir && !hostInfo.IsDir) || resolvedToHostParentDir { // If we're having a file-to-file copy, make sure to // rename accordingly. putOptions.Rename = map[string]string{filepath.Base(containerTarget): hostBaseName} @@ -263,42 +345,9 @@ func copyToContainer(container string, containerPath string, hostPath string) er 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) + containerBaseName, containerInfo, containerResolvedToParentDir, err := resolvePathOnDestinationContainer(container, containerPath, isStdin) + if err != nil { + return err } // If we copy a directory via the "." notation and the container path @@ -310,7 +359,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er // exist, we copy the source's parent and let the copier package create // the destination via the Rename option. hostTarget := hostInfo.LinkTarget - if containerInfoErr != nil && hostInfo.IsDir && strings.HasSuffix(hostTarget, ".") { + if containerResolvedToParentDir && hostInfo.IsDir && strings.HasSuffix(hostTarget, ".") { hostTarget = filepath.Dir(hostTarget) } @@ -362,7 +411,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er // copy the base directory. KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostTarget) != ".", } - if (!hostInfo.IsDir && !containerInfo.IsDir) || containerInfoErr != nil { + if (!hostInfo.IsDir && !containerInfo.IsDir) || containerResolvedToParentDir { // If we're having a file-to-file copy, make sure to // rename accordingly. getOptions.Rename = map[string]string{filepath.Base(hostTarget): containerBaseName} @@ -393,6 +442,52 @@ func copyToContainer(container string, containerPath string, hostPath string) er return doCopy(hostCopy, containerCopy) } +// resolvePathOnDestinationContainer resolves the specified path on the +// container. If the path does not exist, it attempts to use the parent +// directory. +func resolvePathOnDestinationContainer(container string, containerPath string, isStdin bool) (baseName string, containerInfo *entities.ContainerStatReport, resolvedToParentDir bool, err error) { + containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, containerPath) + if err == nil { + baseName = filepath.Base(containerInfo.LinkTarget) + return + } + + if strings.HasSuffix(containerPath, "/") { + err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + return + } + if isStdin { + err = errors.New("destination must be a directory when copying from stdin") + return + } + + // 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 { + baseName = filepath.Base(containerInfo.LinkTarget) + path = containerInfo.LinkTarget + } else { + baseName = filepath.Base(containerPath) + } + + parentDir, err := containerParentDir(container, path) + if err != nil { + err = errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, container) + return + } + + containerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), container, parentDir) + if err != nil { + err = errors.Wrapf(err, "%q could not be found on container %s", containerPath, container) + return + } + + resolvedToParentDir = true + return baseName, containerInfo, resolvedToParentDir, nil +} + // 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). diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index b908ea493..3b6f74efa 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -71,6 +71,9 @@ func init() { ) _ = restoreCommand.RegisterFlagCompletionFunc("publish", completion.AutocompleteNone) + flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with --import)") + _ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning) + validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest) } @@ -91,6 +94,9 @@ func restore(cmd *cobra.Command, args []string) error { if restoreOptions.Import == "" && restoreOptions.Name != "" { return errors.Errorf("--name can only be used with --import") } + if restoreOptions.Import == "" && restoreOptions.Pod != "" { + return errors.Errorf("--pod can only be used with --import") + } if restoreOptions.Name != "" && restoreOptions.TCPEstablished { return errors.Errorf("--tcp-established cannot be used with --name") } diff --git a/cmd/podman/login.go b/cmd/podman/login.go index a8dadf5cd..7e853b38d 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -52,6 +52,7 @@ func init() { loginOptions.Stdin = os.Stdin loginOptions.Stdout = os.Stdout loginOptions.AcceptUnspecifiedRegistry = true + loginOptions.AcceptRepositories = true } // Implementation of podman-login. diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 0ee134635..f44b13a1f 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -43,6 +43,7 @@ func init() { logoutOptions.Stdout = os.Stdout logoutOptions.AcceptUnspecifiedRegistry = true + logoutOptions.AcceptRepositories = true } // Implementation of podman-logout. diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go index 25139a3de..b512ba341 100644 --- a/cmd/podman/registry/config.go +++ b/cmd/podman/registry/config.go @@ -15,6 +15,9 @@ import ( ) const ( + // NoMoveProcess used as cobra.Annotation when command doesn't need Podman to be moved to a separate cgroup + NoMoveProcess = "NoMoveProcess" + // ParentNSRequired used as cobra.Annotation when command requires root access ParentNSRequired = "ParentNSRequired" diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 2633e4040..dc4ebb952 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -208,7 +208,8 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { // 3) command doesn't require Parent Namespace _, found := cmd.Annotations[registry.ParentNSRequired] if !registry.IsRemote() && rootless.IsRootless() && !found { - err := registry.ContainerEngine().SetupRootless(registry.Context(), cmd) + _, noMoveProcess := cmd.Annotations[registry.NoMoveProcess] + err := registry.ContainerEngine().SetupRootless(registry.Context(), noMoveProcess) if err != nil { return err } diff --git a/cmd/podman/system/migrate.go b/cmd/podman/system/migrate.go index 9940cd063..b9dc272d7 100644 --- a/cmd/podman/system/migrate.go +++ b/cmd/podman/system/migrate.go @@ -22,7 +22,10 @@ var ( ` migrateCommand = &cobra.Command{ - Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Annotations: map[string]string{ + registry.EngineMode: registry.ABIMode, + registry.NoMoveProcess: registry.NoMoveProcess, + }, Use: "migrate [options]", Args: validate.NoArgs, Short: "Migrate containers", |