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 }