Merge pull request #962 from xanonid/support_copy_with_chown
Support COPY --chown flag
This commit is contained in:
commit
bd59b60f02
|
|
@ -215,7 +215,7 @@ func resolveEnvironmentBuildArgs(arguments []string, resolver func(string) strin
|
|||
// copy Dockerfile to /kaniko/Dockerfile so that if it's specified in the .dockerignore
|
||||
// it won't be copied into the image
|
||||
func copyDockerfile() error {
|
||||
if _, err := util.CopyFile(opts.DockerfilePath, constants.DockerfilePath, ""); err != nil {
|
||||
if _, err := util.CopyFile(opts.DockerfilePath, constants.DockerfilePath, "", util.DoNotChangeUID, util.DoNotChangeGID); err != nil {
|
||||
return errors.Wrap(err, "copying dockerfile")
|
||||
}
|
||||
opts.DockerfilePath = constants.DockerfilePath
|
||||
|
|
|
|||
|
|
@ -44,9 +44,25 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
|||
if c.cmd.From != "" {
|
||||
c.buildcontext = filepath.Join(constants.KanikoDir, c.cmd.From)
|
||||
}
|
||||
var uid, gid int64
|
||||
uid = util.DoNotChangeUID
|
||||
gid = util.DoNotChangeGID
|
||||
|
||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||
|
||||
if c.cmd.Chown != "" {
|
||||
chown, err := util.ResolveEnvironmentReplacement(c.cmd.Chown, replacementEnvs, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid32, gid32, err := util.GetUIDAndGIDFromString(chown, true)
|
||||
uid = int64(uid32)
|
||||
gid = int64(gid32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
srcs, dest, err := util.ResolveEnvAndWildcards(c.cmd.SourcesAndDest, c.buildcontext, replacementEnvs)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -80,7 +96,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
|||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
copiedFiles, err := util.CopyDir(fullPath, destPath, c.buildcontext)
|
||||
copiedFiles, err := util.CopyDir(fullPath, destPath, c.buildcontext, uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -97,7 +113,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
|||
c.snapshotFiles = append(c.snapshotFiles, destPath)
|
||||
} else {
|
||||
// ... Else, we want to copy over a file
|
||||
exclude, err := util.CopyFile(fullPath, destPath, c.buildcontext)
|
||||
exclude, err := util.CopyFile(fullPath, destPath, c.buildcontext, uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
|
|
@ -72,32 +71,10 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui
|
|||
var userStr string
|
||||
// If specified, run the command as a specific user
|
||||
if config.User != "" {
|
||||
userAndGroup := strings.Split(config.User, ":")
|
||||
userStr = userAndGroup[0]
|
||||
var groupStr string
|
||||
if len(userAndGroup) > 1 {
|
||||
groupStr = userAndGroup[1]
|
||||
}
|
||||
|
||||
uidStr, gidStr, err := util.GetUserFromUsername(userStr, groupStr)
|
||||
uid, gid, err := util.GetUIDAndGIDFromString(config.User, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// uid and gid need to be uint32
|
||||
uid64, err := strconv.ParseUint(uidStr, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid := uint32(uid64)
|
||||
var gid uint32
|
||||
if gidStr != "" {
|
||||
gid64, err := strconv.ParseUint(gidStr, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid = uint32(gid64)
|
||||
}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
}
|
||||
cmd.Env = addDefaultHOME(userStr, replacementEnvs)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||
|
|
@ -326,7 +327,34 @@ Loop:
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetUserFromUsername(userStr string, groupStr string) (string, string, error) {
|
||||
// Extract user and group id from a string formatted 'user:group'.
|
||||
// If fallbackToUID is set, the gid is equal to uid if the group is not specified
|
||||
// otherwise gid is set to zero.
|
||||
func GetUIDAndGIDFromString(userGroupString string, fallbackToUID bool) (uint32, uint32, error) {
|
||||
userAndGroup := strings.Split(userGroupString, ":")
|
||||
userStr := userAndGroup[0]
|
||||
var groupStr string
|
||||
if len(userAndGroup) > 1 {
|
||||
groupStr = userAndGroup[1]
|
||||
}
|
||||
|
||||
uidStr, gidStr, err := GetUserFromUsername(userStr, groupStr, fallbackToUID)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// uid and gid need to be fit into uint32
|
||||
uid64, err := strconv.ParseUint(uidStr, 10, 32)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
gid64, err := strconv.ParseUint(gidStr, 10, 32)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return uint32(uid64), uint32(gid64), nil
|
||||
}
|
||||
|
||||
func GetUserFromUsername(userStr string, groupStr string, fallbackToUID bool) (string, string, error) {
|
||||
// Lookup by username
|
||||
userObj, err := Lookup(userStr)
|
||||
if err != nil {
|
||||
|
|
@ -349,7 +377,10 @@ func GetUserFromUsername(userStr string, groupStr string) (string, string, error
|
|||
}
|
||||
|
||||
uid := userObj.Uid
|
||||
gid := ""
|
||||
gid := "0"
|
||||
if fallbackToUID {
|
||||
gid = userObj.Gid
|
||||
}
|
||||
if group != nil {
|
||||
gid = group.Gid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ limitations under the License.
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||
|
|
@ -526,3 +529,35 @@ func TestResolveEnvironmentReplacementList(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetUIDAndGIDFromString(t *testing.T) {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot get current user: %s", err)
|
||||
}
|
||||
groups, err := currentUser.GroupIds()
|
||||
if err != nil || len(groups) == 0 {
|
||||
t.Fatalf("Cannot get groups for current user: %s", err)
|
||||
}
|
||||
primaryGroupObj, err := user.LookupGroupId(groups[0])
|
||||
if err != nil {
|
||||
t.Fatalf("Could not lookup name of group %s: %s", groups[0], err)
|
||||
}
|
||||
primaryGroup := primaryGroupObj.Name
|
||||
|
||||
testCases := []string{
|
||||
fmt.Sprintf("%s:%s", currentUser.Uid, currentUser.Gid),
|
||||
fmt.Sprintf("%s:%s", currentUser.Username, currentUser.Gid),
|
||||
fmt.Sprintf("%s:%s", currentUser.Uid, primaryGroup),
|
||||
fmt.Sprintf("%s:%s", currentUser.Username, primaryGroup),
|
||||
}
|
||||
expectedU, _ := strconv.ParseUint(currentUser.Uid, 10, 32)
|
||||
expectedG, _ := strconv.ParseUint(currentUser.Gid, 10, 32)
|
||||
for _, tt := range testCases {
|
||||
uid, gid, err := GetUIDAndGIDFromString(tt, false)
|
||||
if uid != uint32(expectedU) || gid != uint32(expectedG) || err != nil {
|
||||
t.Errorf("Could not correctly decode %s to uid/gid %d:%d. Result: %d:%d", tt, expectedU, expectedG,
|
||||
uid, gid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -40,6 +41,9 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const DoNotChangeUID = -1
|
||||
const DoNotChangeGID = -1
|
||||
|
||||
type WhitelistEntry struct {
|
||||
Path string
|
||||
PrefixMatchOnly bool
|
||||
|
|
@ -303,7 +307,7 @@ func ExtractFile(dest string, hdr *tar.Header, tr io.Reader) error {
|
|||
currFile.Close()
|
||||
case tar.TypeDir:
|
||||
logrus.Tracef("creating dir %s", path)
|
||||
if err := mkdirAllWithPermissions(path, mode, uid, gid); err != nil {
|
||||
if err := mkdirAllWithPermissions(path, mode, int64(uid), int64(gid)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -538,9 +542,21 @@ func DownloadFileToDest(rawurl, dest string) error {
|
|||
return os.Chtimes(dest, mTime, mTime)
|
||||
}
|
||||
|
||||
// DetermineTargetFileOwnership returns the user provided uid/gid combination.
|
||||
// If they are set to -1, the uid/gid from the original file is used.
|
||||
func DetermineTargetFileOwnership(fi os.FileInfo, uid, gid int64) (int64, int64) {
|
||||
if uid <= DoNotChangeUID {
|
||||
uid = int64(fi.Sys().(*syscall.Stat_t).Uid)
|
||||
}
|
||||
if gid <= DoNotChangeGID {
|
||||
gid = int64(fi.Sys().(*syscall.Stat_t).Gid)
|
||||
}
|
||||
return uid, gid
|
||||
}
|
||||
|
||||
// CopyDir copies the file or directory at src to dest
|
||||
// It returns a list of files it copied over
|
||||
func CopyDir(src, dest, buildcontext string) ([]string, error) {
|
||||
func CopyDir(src, dest, buildcontext string, uid, gid int64) ([]string, error) {
|
||||
files, err := RelativeFiles("", src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -562,9 +578,7 @@ func CopyDir(src, dest, buildcontext string) ([]string, error) {
|
|||
logrus.Tracef("Creating directory %s", destPath)
|
||||
|
||||
mode := fi.Mode()
|
||||
uid := int(fi.Sys().(*syscall.Stat_t).Uid)
|
||||
gid := int(fi.Sys().(*syscall.Stat_t).Gid)
|
||||
|
||||
uid, gid = DetermineTargetFileOwnership(fi, uid, gid)
|
||||
if err := mkdirAllWithPermissions(destPath, mode, uid, gid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -575,7 +589,7 @@ func CopyDir(src, dest, buildcontext string) ([]string, error) {
|
|||
}
|
||||
} else {
|
||||
// ... Else, we want to copy over a file
|
||||
if _, err := CopyFile(fullPath, destPath, buildcontext); err != nil {
|
||||
if _, err := CopyFile(fullPath, destPath, buildcontext, uid, gid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -606,7 +620,7 @@ func CopySymlink(src, dest, buildcontext string) (bool, error) {
|
|||
}
|
||||
|
||||
// CopyFile copies the file at src to dest
|
||||
func CopyFile(src, dest, buildcontext string) (bool, error) {
|
||||
func CopyFile(src, dest, buildcontext string, uid, gid int64) (bool, error) {
|
||||
if ExcludeFile(src, buildcontext) {
|
||||
logrus.Debugf("%s found in .dockerignore, ignoring", src)
|
||||
return true, nil
|
||||
|
|
@ -627,9 +641,8 @@ func CopyFile(src, dest, buildcontext string) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
uid := fi.Sys().(*syscall.Stat_t).Uid
|
||||
gid := fi.Sys().(*syscall.Stat_t).Gid
|
||||
return false, CreateFile(dest, srcFile, fi.Mode(), uid, gid)
|
||||
uid, gid = DetermineTargetFileOwnership(fi, uid, gid)
|
||||
return false, CreateFile(dest, srcFile, fi.Mode(), uint32(uid), uint32(gid))
|
||||
}
|
||||
|
||||
// GetExcludedFiles gets a list of files to exclude from the .dockerignore
|
||||
|
|
@ -699,12 +712,15 @@ func Volumes() []string {
|
|||
return volumes
|
||||
}
|
||||
|
||||
func mkdirAllWithPermissions(path string, mode os.FileMode, uid, gid int) error {
|
||||
func mkdirAllWithPermissions(path string, mode os.FileMode, uid, gid int64) error {
|
||||
if err := os.MkdirAll(path, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
if uid > math.MaxUint32 || gid > math.MaxUint32 {
|
||||
// due to https://github.com/golang/go/issues/8537
|
||||
return errors.New(fmt.Sprintf("Numeric User-ID or Group-ID greater than %v are not properly supported.", math.MaxUint32))
|
||||
}
|
||||
if err := os.Chown(path, int(uid), int(gid)); err != nil {
|
||||
return err
|
||||
}
|
||||
// In some cases, MkdirAll doesn't change the permissions, so run Chmod
|
||||
|
|
|
|||
|
|
@ -963,7 +963,7 @@ func Test_CopyFile_skips_self(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ignored, err := CopyFile(tempFile, tempFile, "")
|
||||
ignored, err := CopyFile(tempFile, tempFile, "", DoNotChangeUID, DoNotChangeGID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -1307,6 +1307,12 @@ func TestUpdateWhitelist(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
whitelist = initialWhitelist
|
||||
defer func() { whitelist = initialWhitelist }()
|
||||
sort.Slice(tt.expected, func(i, j int) bool {
|
||||
return tt.expected[i].Path < tt.expected[j].Path
|
||||
})
|
||||
sort.Slice(whitelist, func(i, j int) bool {
|
||||
return whitelist[i].Path < whitelist[j].Path
|
||||
})
|
||||
UpdateWhitelist(tt.whitelistVarRun)
|
||||
sort.Slice(tt.expected, func(i, j int) bool {
|
||||
return tt.expected[i].Path < tt.expected[j].Path
|
||||
|
|
|
|||
Loading…
Reference in New Issue