github.com/sentienttechnologies/studio-go-runner@v0.0.0-20201118202441-6d21f2ced8ee/internal/runner/localstorage.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 the implementation for the storage sub system that will
     6  // be used by the runner to retrieve storage from local storage
     7  
     8  import (
     9  	"archive/tar"
    10  	"bufio"
    11  	"compress/bzip2"
    12  	"compress/gzip"
    13  	"context"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  
    19  	"github.com/go-stack/stack"
    20  
    21  	"github.com/jjeffery/kv" // MIT License
    22  )
    23  
    24  type localStorage struct {
    25  }
    26  
    27  // NewLocalStorage is used to allocate and initialize a struct that acts as a receiver
    28  //
    29  func NewLocalStorage() (s *localStorage, err kv.Error) {
    30  	return &localStorage{}, nil
    31  }
    32  
    33  // Close is a NoP unless overridden
    34  func (s *localStorage) Close() {
    35  }
    36  
    37  // Hash returns a platform specific hash of the contents of the file that can be used by caching and other functions
    38  // to track storage changes etc
    39  //
    40  func (s *localStorage) Hash(ctx context.Context, name string) (hash string, err kv.Error) {
    41  	return filepath.Base(name), nil
    42  }
    43  
    44  // Gather is used to retrieve files prefixed with a specific key.  It is used to retrieve the individual files
    45  // associated with a previous Hoard operation
    46  //
    47  func (s *localStorage) Gather(ctx context.Context, keyPrefix string, outputDir string, tap io.Writer) (warnings []kv.Error, err kv.Error) {
    48  	return warnings, kv.NewError("unimplemented").With("stack", stack.Trace().TrimRuntime())
    49  }
    50  
    51  // Fetch is used to retrieve a file from a well known disk directory and either
    52  // copy it directly into a directory, or unpack the file into the same directory.
    53  //
    54  // Calling this function with output not being a valid directory will result in an error
    55  // being returned.
    56  //
    57  // The tap can be used to make a side copy of the content that is being read.
    58  //
    59  func (s *localStorage) Fetch(ctx context.Context, name string, unpack bool, output string, tap io.Writer) (warns []kv.Error, err kv.Error) {
    60  
    61  	kv := kv.With("output", output).With("name", name)
    62  
    63  	// Make sure output is an existing directory
    64  	info, errGo := os.Stat(output)
    65  	if errGo != nil {
    66  		return warns, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    67  	}
    68  	if !info.IsDir() {
    69  		return warns, kv.NewError(output+" is not a directory").With("stack", stack.Trace().TrimRuntime())
    70  	}
    71  
    72  	fileType, err := MimeFromExt(name)
    73  	if err != nil {
    74  		warns = append(warns, kv.Wrap(err).With("fn", name).With("type", fileType).With("stack", stack.Trace().TrimRuntime()))
    75  	} else {
    76  		warns = append(warns, kv.NewError("debug").With("fn", name).With("type", fileType).With("stack", stack.Trace().TrimRuntime()))
    77  	}
    78  
    79  	obj, errGo := os.Open(filepath.Clean(name))
    80  	if errGo != nil {
    81  		return warns, kv.Wrap(errGo, "could not open file "+name).With("stack", stack.Trace().TrimRuntime())
    82  	}
    83  	defer obj.Close()
    84  
    85  	return fetcher(obj, name, output, fileType, unpack)
    86  }
    87  
    88  func addReader(obj *os.File, fileType string) (inReader io.ReadCloser, err kv.Error) {
    89  	switch fileType {
    90  	case "application/x-gzip", "application/zip":
    91  		reader, errGo := gzip.NewReader(obj)
    92  		if errGo != nil {
    93  			return reader, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
    94  		}
    95  		inReader = reader
    96  	case "application/bzip2", "application/octet-stream":
    97  		inReader = ioutil.NopCloser(bzip2.NewReader(obj))
    98  	default:
    99  		inReader = ioutil.NopCloser(obj)
   100  	}
   101  	return inReader, err
   102  }
   103  
   104  func fetcher(obj *os.File, name string, output string, fileType string, unpack bool) (warns []kv.Error, err kv.Error) {
   105  	// If the unpack flag is set then use a tar decompressor and unpacker
   106  	// but first make sure the output location is an existing directory
   107  	if unpack {
   108  
   109  		inReader, err := addReader(obj, fileType)
   110  		if err != nil {
   111  			return warns, err
   112  		}
   113  		defer inReader.Close()
   114  
   115  		tarReader := tar.NewReader(inReader)
   116  
   117  		for {
   118  			header, errGo := tarReader.Next()
   119  			if errGo == io.EOF {
   120  				break
   121  			} else if errGo != nil {
   122  				return warns, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
   123  			}
   124  
   125  			path := filepath.Join(output, header.Name)
   126  			info := header.FileInfo()
   127  			if info.IsDir() {
   128  				if errGo = os.MkdirAll(path, info.Mode()); errGo != nil {
   129  					return warns, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
   130  				}
   131  				continue
   132  			}
   133  
   134  			file, errGo := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
   135  			if errGo != nil {
   136  				return warns, kv.Wrap(errGo).With("file", path).With("stack", stack.Trace().TrimRuntime())
   137  			}
   138  
   139  			_, errGo = io.Copy(file, tarReader)
   140  			file.Close()
   141  			if errGo != nil {
   142  				return warns, kv.Wrap(errGo).With("stack", stack.Trace().TrimRuntime())
   143  			}
   144  		}
   145  	} else {
   146  		fn := filepath.Join(output, filepath.Base(name))
   147  		f, errGo := os.Create(fn)
   148  		if errGo != nil {
   149  			return warns, kv.Wrap(errGo).With("outputFile", fn).With("stack", stack.Trace().TrimRuntime())
   150  		}
   151  		defer f.Close()
   152  
   153  		outf := bufio.NewWriter(f)
   154  		if _, errGo = io.Copy(outf, obj); errGo != nil {
   155  			return warns, kv.Wrap(errGo).With("outputFile", fn).With("stack", stack.Trace().TrimRuntime())
   156  		}
   157  		outf.Flush()
   158  	}
   159  	return warns, nil
   160  }
   161  
   162  // Hoard is not a supported feature of local caching
   163  //
   164  func (s *localStorage) Hoard(ctx context.Context, src string, destPrefix string) (warns []kv.Error, err kv.Error) {
   165  	return warns, kv.NewError("localized storage caches do not support write through saving of files").With("stack", stack.Trace().TrimRuntime())
   166  }
   167  
   168  // Deposit is not a supported feature of local caching
   169  //
   170  func (s *localStorage) Deposit(ctx context.Context, src string, dest string) (warns []kv.Error, err kv.Error) {
   171  	return warns, kv.NewError("localized storage caches do not support write through saving of files").With("stack", stack.Trace().TrimRuntime())
   172  }