...

Source file src/github.com/redhat-developer/odo/pkg/sync/copy.go

Documentation: github.com/redhat-developer/odo/pkg/sync

     1  package sync
     2  
     3  import (
     4  	taro "archive/tar"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/redhat-developer/odo/pkg/log"
    15  	"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
    16  	"github.com/redhat-developer/odo/pkg/util"
    17  
    18  	dfutil "github.com/devfile/library/v2/pkg/util"
    19  	gitignore "github.com/sabhiram/go-gitignore"
    20  
    21  	"k8s.io/klog"
    22  )
    23  
    24  // CopyFile copies localPath directory or list of files in copyFiles list to the directory in running Pod.
    25  // copyFiles is list of changed files captured during `odo watch` as well as binary file path
    26  // During copying binary components, localPath represent base directory path to binary and copyFiles contains path of binary
    27  // During copying local source components, localPath represent base directory path whereas copyFiles is empty
    28  // During `odo watch`, localPath represent base directory path whereas copyFiles contains list of changed Files
    29  func (a SyncClient) CopyFile(ctx context.Context, localPath string, compInfo ComponentInfo, targetPath string, copyFiles []string, globExps []string, ret util.IndexerRet) error {
    30  
    31  	// Destination is set to "ToSlash" as all containers being ran within OpenShift / S2I are all
    32  	// Linux based and thus: "\opt\app-root\src" would not work correctly.
    33  	dest := filepath.ToSlash(filepath.Join(targetPath, filepath.Base(localPath)))
    34  	targetPath = filepath.ToSlash(targetPath)
    35  
    36  	klog.V(4).Infof("CopyFile arguments: localPath %s, dest %s, targetPath %s, copyFiles %s, globalExps %s", localPath, dest, targetPath, copyFiles, globExps)
    37  	reader, writer := io.Pipe()
    38  	// inspired from https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L235
    39  	go func() {
    40  		defer writer.Close()
    41  
    42  		err := makeTar(localPath, dest, writer, copyFiles, globExps, ret, filesystem.DefaultFs{})
    43  		if err != nil {
    44  			log.Errorf("Error while creating tar: %#v", err)
    45  			os.Exit(1)
    46  		}
    47  	}()
    48  
    49  	err := a.ExtractProjectToComponent(ctx, compInfo.ContainerName, compInfo.PodName, targetPath, reader)
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	return nil
    55  }
    56  
    57  // ExtractProjectToComponent extracts the project archive(tar) to the target path from the reader stdin
    58  func (a SyncClient) ExtractProjectToComponent(ctx context.Context, containerName, podName, targetPath string, stdin io.Reader) error {
    59  	// cmdArr will run inside container
    60  	cmdArr := []string{"tar", "xf", "-", "-C", targetPath, "--no-same-owner"}
    61  	var stdout bytes.Buffer
    62  	var stderr bytes.Buffer
    63  	klog.V(3).Infof("Executing command %s", strings.Join(cmdArr, " "))
    64  	err := a.platformClient.ExecCMDInContainer(ctx, containerName, podName, cmdArr, &stdout, &stderr, stdin, false)
    65  	if err != nil {
    66  		log.Errorf("Command '%s' in container failed.\n", strings.Join(cmdArr, " "))
    67  		log.Errorf("stdout: %s\n", stdout.String())
    68  		log.Errorf("stderr: %s\n", stderr.String())
    69  		log.Errorf("err: %s\n", err.Error())
    70  		if exiterr, ok := err.(*exec.ExitError); ok {
    71  			log.Errorf("exit err: %s\n", string(exiterr.Stderr))
    72  		}
    73  	}
    74  	return err
    75  }
    76  
    77  // checkFileExist check if given file exists or not
    78  func checkFileExistWithFS(fileName string, fs filesystem.Filesystem) bool {
    79  	_, err := fs.Stat(fileName)
    80  	return !os.IsNotExist(err)
    81  }
    82  
    83  // makeTar function is copied from https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L309
    84  // srcPath is ignored if files is set
    85  func makeTar(srcPath, destPath string, writer io.Writer, files []string, globExps []string, ret util.IndexerRet, fs filesystem.Filesystem) error {
    86  	// TODO: use compression here?
    87  	tarWriter := taro.NewWriter(writer)
    88  	defer tarWriter.Close()
    89  	srcPath = filepath.Clean(srcPath)
    90  
    91  	// "ToSlash" is used as all containers within OpenShift are Linux based
    92  	// and thus \opt\app-root\src would be an invalid path. Backward slashes
    93  	// are converted to forward.
    94  	destPath = filepath.ToSlash(filepath.Clean(destPath))
    95  	uniquePaths := make(map[string]bool)
    96  	klog.V(4).Infof("makeTar arguments: srcPath: %s, destPath: %s, files: %+v", srcPath, destPath, files)
    97  	if len(files) != 0 {
    98  		ignoreMatcher := gitignore.CompileIgnoreLines(globExps...)
    99  		for _, fileName := range files {
   100  
   101  			if _, ok := uniquePaths[fileName]; ok {
   102  				continue
   103  			} else {
   104  				uniquePaths[fileName] = true
   105  			}
   106  
   107  			if checkFileExistWithFS(fileName, fs) {
   108  
   109  				rel, err := filepath.Rel(srcPath, fileName)
   110  				if err != nil {
   111  					return err
   112  				}
   113  
   114  				matched := ignoreMatcher.MatchesPath(rel)
   115  				if matched {
   116  					continue
   117  				}
   118  
   119  				// Fetch path of source file relative to that of source base path so that it can be passed to recursiveTar
   120  				// which uses path relative to base path for taro header to correctly identify file location when untarred
   121  
   122  				// now that the file exists, now we need to get the absolute path
   123  				fileAbsolutePath, err := dfutil.GetAbsPath(fileName)
   124  				if err != nil {
   125  					return err
   126  				}
   127  				klog.V(4).Infof("Got abs path: %s", fileAbsolutePath)
   128  				klog.V(4).Infof("Making %s relative to %s", srcPath, fileAbsolutePath)
   129  
   130  				// We use "FromSlash" to make this OS-based (Windows uses \, Linux & macOS use /)
   131  				// we get the relative path by joining the two
   132  				destFile, err := filepath.Rel(filepath.FromSlash(srcPath), filepath.FromSlash(fileAbsolutePath))
   133  				if err != nil {
   134  					return err
   135  				}
   136  
   137  				// Now we get the source file and join it to the base directory.
   138  				srcFile := filepath.Join(filepath.Base(srcPath), destFile)
   139  
   140  				if value, ok := ret.NewFileMap[destFile]; ok && value.RemoteAttribute != "" {
   141  					destFile = value.RemoteAttribute
   142  				}
   143  
   144  				klog.V(4).Infof("makeTar srcFile: %s", srcFile)
   145  				klog.V(4).Infof("makeTar destFile: %s", destFile)
   146  
   147  				// The file could be a regular file or even a folder, so use recursiveTar which handles symlinks, regular files and folders
   148  				err = linearTar(filepath.Dir(srcPath), srcFile, filepath.Dir(destPath), destFile, tarWriter, fs)
   149  				if err != nil {
   150  					return err
   151  				}
   152  			}
   153  		}
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  // linearTar function is a modified version of https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cp.go#L319
   160  func linearTar(srcBase, srcFile, destBase, destFile string, tw *taro.Writer, fs filesystem.Filesystem) error {
   161  	if destFile == "" {
   162  		return fmt.Errorf("linear Tar error, destFile cannot be empty")
   163  	}
   164  
   165  	klog.V(4).Infof("recursiveTar arguments: srcBase: %s, srcFile: %s, destBase: %s, destFile: %s", srcBase, srcFile, destBase, destFile)
   166  
   167  	// The destination is a LINUX container and thus we *must* use ToSlash in order
   168  	// to get the copying over done correctly..
   169  	destBase = filepath.ToSlash(destBase)
   170  	destFile = filepath.ToSlash(destFile)
   171  	klog.V(4).Infof("Corrected destinations: base: %s file: %s", destBase, destFile)
   172  
   173  	joinedPath := filepath.Join(srcBase, srcFile)
   174  
   175  	stat, err := fs.Stat(joinedPath)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	if stat.IsDir() {
   181  		files, err := fs.ReadDir(joinedPath)
   182  		if err != nil {
   183  			return err
   184  		}
   185  		if len(files) == 0 {
   186  			// case empty directory
   187  			hdr, _ := taro.FileInfoHeader(stat, joinedPath)
   188  			hdr.Name = destFile
   189  			if err := tw.WriteHeader(hdr); err != nil {
   190  				return err
   191  			}
   192  		}
   193  		return nil
   194  	} else if stat.Mode()&os.ModeSymlink != 0 {
   195  		// case soft link
   196  		hdr, _ := taro.FileInfoHeader(stat, joinedPath)
   197  		target, err := os.Readlink(joinedPath)
   198  		if err != nil {
   199  			return err
   200  		}
   201  
   202  		hdr.Linkname = target
   203  		hdr.Name = destFile
   204  		if err := tw.WriteHeader(hdr); err != nil {
   205  			return err
   206  		}
   207  	} else {
   208  		// case regular file or other file type like pipe
   209  		hdr, err := taro.FileInfoHeader(stat, joinedPath)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		hdr.Name = destFile
   214  
   215  		err = tw.WriteHeader(hdr)
   216  		if err != nil {
   217  			return err
   218  		}
   219  
   220  		f, err := fs.Open(joinedPath)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		defer f.Close() // #nosec G307
   225  
   226  		if _, err := io.Copy(tw, f); err != nil {
   227  			return err
   228  		}
   229  
   230  		return f.Close()
   231  	}
   232  
   233  	return nil
   234  }
   235  

View as plain text