summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/images/scp.go31
-rw-r--r--docs/source/markdown/podman-build.1.md8
-rw-r--r--docs/source/markdown/podman-create.1.md9
-rw-r--r--docs/source/markdown/podman-image-scp.1.md18
-rw-r--r--docs/source/markdown/podman-run.1.md9
-rw-r--r--libpod/container_internal.go24
-rw-r--r--libpod/runtime_ctr.go9
-rw-r--r--libpod/runtime_pod_linux.go40
-rw-r--r--pkg/domain/entities/engine_image.go1
-rw-r--r--pkg/domain/entities/images.go4
-rw-r--r--pkg/domain/infra/abi/images.go65
-rw-r--r--pkg/domain/infra/tunnel/images.go5
-rw-r--r--test/e2e/image_scp_test.go22
13 files changed, 213 insertions, 32 deletions
diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go
index c89a090bf..8402d9a10 100644
--- a/cmd/podman/images/scp.go
+++ b/cmd/podman/images/scp.go
@@ -16,6 +16,7 @@ import (
"github.com/containers/podman/v3/cmd/podman/system/connection"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
+ "github.com/containers/podman/v3/pkg/rootless"
"github.com/docker/distribution/reference"
scpD "github.com/dtylman/scp"
"github.com/pkg/errors"
@@ -125,6 +126,11 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
fmt.Println(rep)
// TODO: Add podman remote support
default: // else native load
+ scpOpts.Save.Format = "oci-archive"
+ _, err := os.Open(scpOpts.Save.Output)
+ if err != nil {
+ return err
+ }
if scpOpts.Tag != "" {
return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported")
}
@@ -133,12 +139,20 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) {
if abiErr != nil {
errors.Wrapf(abiErr, "could not save image as specified")
}
- rep, err := abiEng.Load(context.Background(), scpOpts.Load)
- if err != nil {
- return err
+ if !rootless.IsRootless() && scpOpts.Rootless {
+ err := abiEng.Transfer(context.Background(), scpOpts)
+ if err != nil {
+ return err
+ }
+ } else {
+ rep, err := abiEng.Load(context.Background(), scpOpts.Load)
+ if err != nil {
+ return err
+ }
+ fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ","))
}
- fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ","))
}
+
return nil
}
@@ -271,7 +285,14 @@ func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination
scpOpts.SourceImageName = args[0]
}
case 2:
- if strings.Contains(args[0], "::") {
+ if strings.Contains(args[0], "localhost") || strings.Contains(args[1], "localhost") { // only supporting root to local using sudo at the moment
+ scpOpts.Rootless = true
+ scpOpts.User = strings.Split(args[1], "@")[0]
+ scpOpts.SourceImageName = strings.Split(args[0], "::")[1]
+ if strings.Split(args[0], "@")[0] != "root" {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "cannot transfer images from any user besides root using sudo")
+ }
+ } else if strings.Contains(args[0], "::") {
if !(strings.Contains(args[1], "::")) && remoteArgLength(args[0], 1) == 0 { // if an image is specified, this mean we are loading to our client
cliConnections = append(cliConnections, args[0])
scpOpts.ToRemote = true
diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md
index 5a867c574..835df7693 100644
--- a/docs/source/markdown/podman-build.1.md
+++ b/docs/source/markdown/podman-build.1.md
@@ -774,6 +774,14 @@ content label. Shared volume labels allow all containers to read/write content.
The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
+Note: Do not relabel system files and directories. Relabeling system content
+might cause other confined services on your machine to fail. For these types
+of containers, disabling SELinux separation is recommended. The option
+`--security-opt label=disable` disables SELinux separation for the container.
+For example, if a user wanted to volume mount their entire home directory into the build containers, they need to disable SELinux separation.
+
+ $ podman build --security-opt label=disable -v $HOME:/home/user .
+
`Overlay Volume Mounts`
The `:O` flag tells Podman to mount the directory from the host as a
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 3ff736adb..9a37a1dd0 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -1249,6 +1249,15 @@ content label. Shared volume labels allow all containers to read/write content.
The `Z` option tells Podman to label the content with a private unshared label.
Only the current container can use a private volume.
+Note: Do not relabel system files and directories. Relabeling system content
+might cause other confined services on your machine to fail. For these types
+of containers we recommend that disable SELinux separation. The option
+`--security-opt label=disable` disables SELinux separation for containers used in the build.
+For example if a user wanted to volume mount their entire home directory into a
+container, they need to disable SELinux separation.
+
+ $ podman create --security-opt label=disable -v $HOME:/home/user fedora touch /home/user/file
+
`Overlay Volume Mounts`
The `:O` flag tells Podman to mount the directory from the host as a
diff --git a/docs/source/markdown/podman-image-scp.1.md b/docs/source/markdown/podman-image-scp.1.md
index 420452a4d..4dd79f3d2 100644
--- a/docs/source/markdown/podman-image-scp.1.md
+++ b/docs/source/markdown/podman-image-scp.1.md
@@ -8,7 +8,7 @@ podman-image-scp - Securely copy an image from one host to another
## DESCRIPTION
**podman image scp** copies container images between hosts on a network. You can load to the remote host or from the remote host as well as in between two remote hosts.
-Note: `::` is used to specify the image name depending on if you are saving or loading.
+Note: `::` is used to specify the image name depending on if you are saving or loading. Images can also be transferred from rootful to rootless storage on the same machine without using sshd. This feature is not supported on the remote client.
**podman image scp [GLOBAL OPTIONS]**
@@ -62,6 +62,22 @@ Storing signatures
Loaded image(s): docker.io/library/alpine:latest
```
+```
+$ sudo podman image scp root@localhost::alpine username@localhost::
+Copying blob e2eb06d8af82 done
+Copying config 696d33ca15 done
+Writing manifest to image destination
+Storing signatures
+Run Directory Obtained: /run/user/1000/
+[Run Root: /var/tmp/containers-user-1000/containers Graph Root: /root/.local/share/containers/storage DB Path: /root/.local/share/containers/storage/libpod/bolt_state.db]
+Getting image source signatures
+Copying blob 5eb901baf107 skipped: already exists
+Copying config 696d33ca15 done
+Writing manifest to image destination
+Storing signatures
+Loaded image(s): docker.io/library/alpine:latest
+```
+
## SEE ALSO
podman(1), podman-load(1), podman-save(1), podman-remote(1), podman-system-connection-add(1), containers.conf(5), containers-transports(5)
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index a1170253f..ce1e86afe 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -1314,6 +1314,15 @@ share the volume content. As a result, Podman labels the content with a shared
content label. Shared volume labels allow all containers to read/write content.
The **Z** option tells Podman to label the content with a private unshared label.
+Note: Do not relabel system files and directories. Relabeling system content
+might cause other confined services on your machine to fail. For these types
+of containers we recommend that disable SELinux separation. The option
+`--security-opt label=disable` disables SELinux separation for the container.
+For example if a user wanted to volume mount their entire home directory into a
+container, they need to disable SELinux separation.
+
+ $ podman run --security-opt label=disable -v $HOME:/home/user fedora touch /home/user/file
+
`Overlay Volume Mounts`
The `:O` flag tells Podman to mount the directory from the host as a
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 19b48e14b..fbc2c1f38 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -496,9 +496,27 @@ func (c *Container) setupStorage(ctx context.Context) error {
c.setupStorageMapping(&options.IDMappingOptions, &c.config.IDMappings)
- containerInfo, err := c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, options)
- if err != nil {
- return errors.Wrapf(err, "error creating container storage")
+ // Unless the user has specified a name, use a randomly generated one.
+ // Note that name conflicts may occur (see #11735), so we need to loop.
+ generateName := c.config.Name == ""
+ var containerInfo ContainerInfo
+ var containerInfoErr error
+ for {
+ if generateName {
+ name, err := c.runtime.generateName()
+ if err != nil {
+ return err
+ }
+ c.config.Name = name
+ }
+ containerInfo, containerInfoErr = c.runtime.storageService.CreateContainerStorage(ctx, c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, options)
+
+ if !generateName || errors.Cause(containerInfoErr) != storage.ErrDuplicateName {
+ break
+ }
+ }
+ if containerInfoErr != nil {
+ return errors.Wrapf(containerInfoErr, "error creating container storage")
}
// only reconfig IDMappings if layer was mounted from storage
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 0a7db33f1..114bf9315 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -326,15 +326,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
}
}
- if ctr.config.Name == "" {
- name, err := r.generateName()
- if err != nil {
- return nil, err
- }
-
- ctr.config.Name = name
- }
-
// Check CGroup parent sanity, and set it if it was not set.
// Only if we're actually configuring CGroups.
if !ctr.config.NoCgroups {
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 7d7fef4d1..15050ef48 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -43,18 +43,6 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
}
}
- if pod.config.Name == "" {
- name, err := r.generateName()
- if err != nil {
- return nil, err
- }
- pod.config.Name = name
- }
-
- if p.InfraContainerSpec != nil && p.InfraContainerSpec.Hostname == "" {
- p.InfraContainerSpec.Hostname = pod.config.Name
- }
-
// Allocate a lock for the pod
lock, err := r.lockManager.AllocateLock()
if err != nil {
@@ -131,9 +119,33 @@ func (r *Runtime) NewPod(ctx context.Context, p specgen.PodSpecGenerator, option
logrus.Infof("Pod has an infra container, but shares no namespaces")
}
- if err := r.state.AddPod(pod); err != nil {
- return nil, errors.Wrapf(err, "error adding pod to state")
+ // Unless the user has specified a name, use a randomly generated one.
+ // Note that name conflicts may occur (see #11735), so we need to loop.
+ generateName := pod.config.Name == ""
+ var addPodErr error
+ for {
+ if generateName {
+ name, err := r.generateName()
+ if err != nil {
+ return nil, err
+ }
+ pod.config.Name = name
+ }
+
+ if p.InfraContainerSpec != nil && p.InfraContainerSpec.Hostname == "" {
+ p.InfraContainerSpec.Hostname = pod.config.Name
+ }
+ if addPodErr = r.state.AddPod(pod); addPodErr == nil {
+ return pod, nil
+ }
+ if !generateName || (errors.Cause(addPodErr) != define.ErrPodExists && errors.Cause(addPodErr) != define.ErrCtrExists) {
+ break
+ }
+ }
+ if addPodErr != nil {
+ return nil, errors.Wrapf(addPodErr, "error adding pod to state")
}
+
return pod, nil
}
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index b0f9ae408..d72f64b5e 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -27,6 +27,7 @@ type ImageEngine interface {
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
+ Transfer(ctx context.Context, scpOpts ImageScpOptions) error
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 38cdc8f2f..7583ce442 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -329,6 +329,10 @@ type ImageScpOptions struct {
Save ImageSaveOptions
// Load options used for the second half of the scp operation
Load ImageLoadOptions
+ // Rootless determines whether we are loading locally from root storage to rootless storage
+ Rootless bool
+ // User is used in conjunction with Rootless to determine which user to use to obtain the uid
+ User string
}
// ImageTreeOptions provides options for ImageEngine.Tree()
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 7aa202334..5c0227986 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -6,9 +6,12 @@ import (
"io/ioutil"
"net/url"
"os"
+ "os/exec"
+ "os/user"
"path"
"path/filepath"
"strconv"
+ "strings"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
@@ -18,6 +21,7 @@ import (
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/entities/reports"
domainUtils "github.com/containers/podman/v3/pkg/domain/utils"
@@ -330,6 +334,67 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
return pushError
}
+// Transfer moves images from root to rootless storage so the user specified in the scp call can access and use the image modified by root
+func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+ if scpOpts.User == "" {
+ return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
+ }
+ var u *user.User
+ scpOpts.User = strings.Split(scpOpts.User, ":")[0] // split in case provided with uid:gid
+ _, err := strconv.Atoi(scpOpts.User)
+ if err != nil {
+ u, err = user.Lookup(scpOpts.User)
+ if err != nil {
+ return err
+ }
+ } else {
+ u, err = user.LookupId(scpOpts.User)
+ if err != nil {
+ return err
+ }
+ }
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ return err
+ }
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return err
+ }
+ err = os.Chown(scpOpts.Save.Output, uid, gid) // chown the output because was created by root so we need to give th euser read access
+ if err != nil {
+ return err
+ }
+
+ podman, err := os.Executable()
+ if err != nil {
+ return err
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
+ cmd := exec.Command("su", "-l", u.Username, "--command", podman+" --log-level="+logrus.GetLevel().String()+" --cgroup-manager=cgroupfs load --input="+scpOpts.Save.Output) // load the new image to the rootless storage
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ logrus.Debug("Executing load command su")
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+ } else {
+ cmd := exec.Command(machinectl, "shell", "-q", u.Username+"@.host", podman, "--log-level="+logrus.GetLevel().String(), "--cgroup-manager=cgroupfs", "load", "--input", scpOpts.Save.Output) // load the new image to the rootless storage
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = os.Stdout
+ logrus.Debug("Executing load command machinectl")
+ err = cmd.Run()
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
// Allow tagging manifest list instead of resolving instances from manifest
lookupOptions := &libimage.LookupImageOptions{ManifestList: true}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index e17f746a5..fde57972f 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/bindings/images"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/entities/reports"
@@ -122,6 +123,10 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.
return &entities.ImagePullReport{Images: pulledImages}, nil
}
+func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+ return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage")
+}
+
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, opt entities.ImageTagOptions) error {
options := new(images.TagOptions)
for _, newTag := range tags {
diff --git a/test/e2e/image_scp_test.go b/test/e2e/image_scp_test.go
index 9fd8d7e27..acea2993d 100644
--- a/test/e2e/image_scp_test.go
+++ b/test/e2e/image_scp_test.go
@@ -22,12 +22,14 @@ var _ = Describe("podman image scp", func() {
)
BeforeEach(func() {
+
ConfPath.Value, ConfPath.IsSet = os.LookupEnv("CONTAINERS_CONF")
conf, err := ioutil.TempFile("", "containersconf")
if err != nil {
panic(err)
}
os.Setenv("CONTAINERS_CONF", conf.Name())
+
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@@ -38,6 +40,7 @@ var _ = Describe("podman image scp", func() {
AfterEach(func() {
podmanTest.Cleanup()
+
os.Remove(os.Getenv("CONTAINERS_CONF"))
if ConfPath.IsSet {
os.Setenv("CONTAINERS_CONF", ConfPath.Value)
@@ -58,6 +61,25 @@ var _ = Describe("podman image scp", func() {
Expect(scp).To(Exit(0))
})
+ It("podman image scp root to rootless transfer", func() {
+ SkipIfNotRootless("this is a rootless only test, transfering from root to rootless using PodmanAsUser")
+ if IsRemote() {
+ Skip("this test is only for non-remote")
+ }
+ env := os.Environ()
+ img := podmanTest.PodmanAsUser([]string{"image", "pull", ALPINE}, 0, 0, "", env) // pull image to root
+ img.WaitWithDefaultTimeout()
+ Expect(img).To(Exit(0))
+ scp := podmanTest.PodmanAsUser([]string{"image", "scp", "root@localhost::" + ALPINE, "1000:1000@localhost::"}, 0, 0, "", env) //transfer from root to rootless (us)
+ scp.WaitWithDefaultTimeout()
+ Expect(scp).To(Exit(0))
+
+ list := podmanTest.Podman([]string{"image", "list"}) // our image should now contain alpine loaded in from root
+ list.WaitWithDefaultTimeout()
+ Expect(list).To(Exit(0))
+ Expect(list.LineInOutputStartsWith("quay.io/libpod/alpine")).To(BeTrue())
+ })
+
It("podman image scp bogus image", func() {
if IsRemote() {
Skip("this test is only for non-remote")