github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/tar.go (about)

     1  // Copyright 2018-2020 (c) Cognizant Digital Business, Evolutionary AI. All rights reserved. Issued under the Apache 2.0 License.
     2  
     3  package runner
     4  
     5  // This file contains implementations of some tar handling functions and methods to add a little
     6  // structure around tar file handling when specifically writing files into archives on streaming
     7  // devices or file systems
     8  
     9  import (
    10  	"archive/tar"
    11  	"io"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/go-stack/stack"
    17  	"github.com/jjeffery/kv" // MIT License
    18  )
    19  
    20  // TarWriter encapsulates a writer of tar files that stores the source dir and the headers that
    21  // will be used to generate a studioml artifact
    22  type TarWriter struct {
    23  	dir   string
    24  	files map[string]*tar.Header
    25  }
    26  
    27  // NewTarWriter generates a data structure to encapsulate the tar headers for the
    28  // files within a caller specified directory that can be used to generate an artifact
    29  //
    30  func NewTarWriter(dir string) (t *TarWriter, err kv.Error) {
    31  
    32  	t = &TarWriter{
    33  		dir:   dir,
    34  		files: map[string]*tar.Header{},
    35  	}
    36  
    37  	errGo := filepath.Walk(dir, func(file string, fi os.FileInfo, err error) error {
    38  
    39  		// return on any error
    40  		if err != nil {
    41  			return err
    42  		}
    43  
    44  		link := ""
    45  		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
    46  			if link, err = os.Readlink(file); err != nil {
    47  				return kv.Wrap(err).With("stack", stack.Trace().TrimRuntime())
    48  			}
    49  		}
    50  
    51  		// create a new dir/file header
    52  		header, err := tar.FileInfoHeader(fi, link)
    53  		if err != nil {
    54  			return kv.Wrap(err).With("stack", stack.Trace().TrimRuntime())
    55  		}
    56  
    57  		// update the name to correctly reflect the desired destination when untaring
    58  		header.Name = strings.TrimPrefix(strings.Replace(file, dir, "", -1), string(filepath.Separator))
    59  
    60  		if len(header.Name) == 0 {
    61  			// Our output directory proper, ignore it
    62  			return nil
    63  		}
    64  
    65  		t.files[file] = header
    66  
    67  		return nil
    68  	})
    69  
    70  	if errGo != nil {
    71  		err, ok := errGo.(kv.Error)
    72  		if ok {
    73  			return nil, err
    74  		}
    75  		return nil, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    76  	}
    77  
    78  	return t, nil
    79  }
    80  
    81  // HasFiles is used to test the artifact file catalog to see if there are files
    82  // within it
    83  //
    84  func (t *TarWriter) HasFiles() bool {
    85  	return len(t.files) != 0
    86  }
    87  
    88  // Write is used to add a go tar file writer device to the
    89  // tar writer and to output the files within the catalog of the
    90  // runners file list into the go tar device
    91  //
    92  func (t *TarWriter) Write(tw *tar.Writer) (err kv.Error) {
    93  
    94  	for file, header := range t.files {
    95  		err = func() (err kv.Error) {
    96  			// return on directories since there will be no content to tar, only headers
    97  			fi, errGo := os.Stat(file)
    98  			if errGo != nil {
    99  				// Working files can be recycled on occasion and disappear, handle this
   100  				// possibility
   101  				if os.IsNotExist(errGo) {
   102  					return nil
   103  				}
   104  				return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()).With("file", file)
   105  			}
   106  
   107  			// open files for taring, skip files that could not be opened, this could be due to working
   108  			// files getting scratched etc and is legal
   109  			f, errGo := os.Open(filepath.Clean(file))
   110  			if errGo != nil {
   111  				return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()).With("file", file)
   112  			}
   113  			defer func() { _ = f.Close() }()
   114  
   115  			// write the header
   116  			if errGo := tw.WriteHeader(header); errGo != nil {
   117  				return kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime()).With("file", file)
   118  			}
   119  
   120  			if !fi.Mode().IsRegular() {
   121  				return nil
   122  			}
   123  
   124  			// copy file data into tar writer
   125  			if _, err := io.CopyN(tw, f, header.Size); err != nil {
   126  				return kv.Wrap(err).With("stack", stack.Trace().TrimRuntime()).With("file", file)
   127  			}
   128  			return nil
   129  		}()
   130  		if err != nil {
   131  			return err
   132  		}
   133  	}
   134  	return nil
   135  }