Add --chown option to add/copy commands

Signed-off-by: Fabio Bertinatto <fbertina@redhat.com>

Closes: #336
Approved by: rhatdan
This commit is contained in:
Fabio Bertinatto 2017-11-30 15:34:02 +01:00 committed by Atomic Bot
parent 77804bf256
commit 1fc5a49958
6 changed files with 109 additions and 4 deletions

55
add.go
View File

@ -12,10 +12,16 @@ import (
"time"
"github.com/containers/storage/pkg/archive"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
//AddAndCopyOptions holds options for add and copy commands.
type AddAndCopyOptions struct {
Chown string
}
// addURL copies the contents of the source URL to the destination. This is
// its own function so that deferred closes happen after we're done pulling
// down each item of potentially many.
@ -58,7 +64,7 @@ func addURL(destination, srcurl string) error {
// Add copies the contents of the specified sources into the container's root
// filesystem, optionally extracting contents of local files that look like
// non-empty archives.
func (b *Builder) Add(destination string, extract bool, source ...string) error {
func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, source ...string) error {
mountPoint, err := b.Mount(b.MountLabel)
if err != nil {
return err
@ -100,6 +106,11 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if len(source) > 1 && (destfi == nil || !destfi.IsDir()) {
return errors.Errorf("destination %q is not a directory", dest)
}
// Find out which user (and group) the destination should belong to.
user, err := b.user(mountPoint, options)
if err != nil {
return err
}
for _, src := range source {
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
// We assume that source is a file, and we're copying
@ -118,6 +129,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := addURL(d, src); err != nil {
return err
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
@ -146,6 +160,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := copyWithTar(gsrc, d); err != nil {
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
if !extract || !archive.IsArchivePath(gsrc) {
@ -161,6 +178,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := copyFileWithTar(gsrc, d); err != nil {
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
// We're extracting an archive into the destination directory.
@ -172,3 +192,36 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
}
return nil
}
// user returns the user (and group) information which the destination should belong to.
func (b *Builder) user(mountPoint string, options AddAndCopyOptions) (specs.User, error) {
if options.Chown != "" {
return getUser(mountPoint, options.Chown)
}
return getUser(mountPoint, b.User())
}
// setOwner sets the uid and gid owners of a given path.
// If path is a directory, recursively changes the owner.
func setOwner(path string, user specs.User) error {
fi, err := os.Stat(path)
if err != nil {
return errors.Wrapf(err, "error reading %q", path)
}
if fi.IsDir() {
err2 := filepath.Walk(path, func(p string, info os.FileInfo, we error) error {
if err3 := os.Lchown(p, int(user.UID), int(user.GID)); err3 != nil {
return errors.Wrapf(err3, "error setting ownership of %q", p)
}
return nil
})
if err2 != nil {
return errors.Wrapf(err2, "error walking dir %q to set ownership", path)
}
return nil
}
if err := os.Lchown(path, int(user.UID), int(user.GID)); err != nil {
return errors.Wrapf(err, "error setting ownership of %q", path)
}
return nil
}

View File

@ -2,10 +2,17 @@ package main
import (
"github.com/pkg/errors"
"github.com/projectatomic/buildah"
"github.com/urfave/cli"
)
var (
addAndCopyFlags = []cli.Flag{
cli.StringFlag{
Name: "chown",
Usage: "Set the user and group ownership of the destination content",
},
}
addDescription = "Adds the contents of a file, URL, or directory to a container's working\n directory. If a local file appears to be an archive, its contents are\n extracted and added instead of the archive file itself."
copyDescription = "Copies the contents of a file, URL, or directory into a container's working\n directory"
@ -13,6 +20,7 @@ var (
Name: "add",
Usage: "Add content to the container",
Description: addDescription,
Flags: addAndCopyFlags,
Action: addCmd,
ArgsUsage: "CONTAINER-NAME-OR-ID [[FILE | DIRECTORY | URL] ...] [DESTINATION]",
}
@ -21,6 +29,7 @@ var (
Name: "copy",
Usage: "Copy content into the container",
Description: copyDescription,
Flags: addAndCopyFlags,
Action: copyCmd,
ArgsUsage: "CONTAINER-NAME-OR-ID [[FILE | DIRECTORY | URL] ...] [DESTINATION]",
}
@ -34,6 +43,10 @@ func addAndCopyCmd(c *cli.Context, extractLocalArchives bool) error {
name := args[0]
args = args.Tail()
if err := validateFlags(c, addAndCopyFlags); err != nil {
return err
}
// If list is greater then one, the last item is the destination
dest := ""
size := len(args)
@ -52,8 +65,11 @@ func addAndCopyCmd(c *cli.Context, extractLocalArchives bool) error {
return errors.Wrapf(err, "error reading build container %q", name)
}
err = builder.Add(dest, extractLocalArchives, args...)
if err != nil {
options := buildah.AddAndCopyOptions{
Chown: c.String("chown"),
}
if err := builder.Add(dest, extractLocalArchives, options, args...); err != nil {
return errors.Wrapf(err, "error adding content to container %q", builder.Container)
}

View File

@ -13,10 +13,18 @@ appears to be an archive, its contents are extracted and added instead of the
archive file itself. If a local directory is specified as a source, its
*contents* are copied to the destination.
## OPTIONS
**--chown** *owner*:*group*
Sets the user and group ownership of the destination content.
## EXAMPLE
buildah add containerID '/myapp/app.conf' '/myapp/app.conf'
buildah add --chown myuser:mygroup containerID '/myapp/app.conf' '/myapp/app.conf'
buildah add containerID '/home/myuser/myproject.go'
buildah add containerID '/home/myuser/myfiles.tar' '/tmp'

View File

@ -11,10 +11,18 @@ Copies the contents of a file, URL, or a directory to a container's working
directory or a specified location in the container. If a local directory is
specified as a source, its *contents* are copied to the destination.
## OPTIONS
**--chown** *owner*:*group*
Sets the user and group ownership of the destination content.
## EXAMPLE
buildah copy containerID '/myapp/app.conf' '/myapp/app.conf'
buildah copy --chown myuser:mygroup containerID '/myapp/app.conf' '/myapp/app.conf'
buildah copy containerID '/home/myuser/myproject.go'
buildah copy containerID '/home/myuser/myfiles.tar' '/tmp'

View File

@ -341,7 +341,7 @@ func (b *Executor) Copy(excludes []string, copies ...imagebuilder.Copy) error {
sources = append(sources, filepath.Join(b.contextDir, src))
}
}
if err := b.builder.Add(copy.Dest, copy.Download, sources...); err != nil {
if err := b.builder.Add(copy.Dest, copy.Download, buildah.AddAndCopyOptions{}, sources...); err != nil {
return err
}
}

View File

@ -111,3 +111,23 @@ load helpers
[ "$status" -ne 0 ]
buildah rm $cid
}
@test "copy --chown" {
mkdir -p ${TESTDIR}/subdir
createrandom ${TESTDIR}/randomfile
createrandom ${TESTDIR}/subdir/randomfile
createrandom ${TESTDIR}/subdir/other-randomfile
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
root=$(buildah mount $cid)
buildah config --workingdir / $cid
buildah copy --chown 1:1 $cid ${TESTDIR}/randomfile
buildah copy --chown root:1 $cid ${TESTDIR}/randomfile /randomfile2
buildah copy --chown nobody $cid ${TESTDIR}/randomfile /randomfile3
buildah copy --chown nobody:root $cid ${TESTDIR}/subdir /subdir
test $(stat -c "%u:%g" $root/randomfile) = "1:1"
test $(stat -c "%U:%g" $root/randomfile2) = "root:1"
test $(stat -c "%U" $root/randomfile3) = "nobody"
(cd $root/subdir/; for i in *; do test $(stat -c "%U:%G" $i) = "nobody:root"; done)
buildah rm $cid
}