github.com/bitrise-io/go-steputils/v2@v2.0.0-alpha.30/cache/keytemplate/checksum.go (about) 1 package keytemplate 2 3 import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "io" 7 "os" 8 "path/filepath" 9 "sort" 10 "strings" 11 12 "github.com/bitrise-io/go-utils/v2/pathutil" 13 "github.com/bmatcuk/doublestar/v4" 14 ) 15 16 // checksum returns a hex-encoded SHA-256 checksum of one or multiple files. Each file path can contain glob patterns, 17 // including "doublestar" patterns (such as `**/*.gradle`). 18 // The path list is sorted alphabetically to produce consistent output. 19 // Errors are logged as warnings and an empty string is returned in that case. 20 func (m Model) checksum(paths ...string) string { 21 files := m.evaluateGlobPatterns(paths) 22 m.logger.Debugf("Files included in checksum:") 23 for _, path := range files { 24 m.logger.Debugf("- %s", path) 25 } 26 27 if len(files) == 0 { 28 m.logger.Warnf("No files to include in the checksum") 29 return "" 30 } else if len(files) == 1 { 31 checksum, err := checksumOfFile(files[0]) 32 if err != nil { 33 m.logger.Warnf("Error while computing checksum %s: %s", files[0], err) 34 return "" 35 } 36 return hex.EncodeToString(checksum) 37 } 38 39 finalChecksum := sha256.New() 40 sort.Strings(files) 41 for _, path := range files { 42 checksum, err := checksumOfFile(path) 43 if err != nil { 44 m.logger.Warnf("Error while hashing %s: %s", path, err) 45 continue 46 } 47 48 finalChecksum.Write(checksum) 49 } 50 51 return hex.EncodeToString(finalChecksum.Sum(nil)) 52 } 53 54 func (m Model) evaluateGlobPatterns(paths []string) []string { 55 var finalPaths []string 56 57 for _, path := range paths { 58 if strings.Contains(path, "*") { 59 base, pattern := doublestar.SplitPattern(path) 60 absBase, err := pathutil.NewPathModifier().AbsPath(base) 61 if err != nil { 62 m.logger.Warnf("Failed to convert %s to an absolute path: %s", path, err) 63 continue 64 } 65 matches, err := doublestar.Glob(os.DirFS(absBase), pattern) 66 if matches == nil { 67 m.logger.Warnf("No match for pattern: %s", path) 68 continue 69 } 70 if err != nil { 71 m.logger.Warnf("Error in pattern '%s': %s", path, err) 72 continue 73 } 74 for _, match := range matches { 75 finalPaths = append(finalPaths, filepath.Join(base, match)) 76 } 77 } else { 78 finalPaths = append(finalPaths, path) 79 } 80 } 81 82 return filterFilesOnly(finalPaths) 83 } 84 85 func checksumOfFile(path string) ([]byte, error) { 86 hash := sha256.New() 87 file, err := os.Open(path) 88 if err != nil { 89 return nil, err 90 } 91 defer file.Close() //nolint:errcheck 92 93 _, err = io.Copy(hash, file) 94 if err != nil { 95 return nil, err 96 } 97 98 return hash.Sum(nil), nil 99 } 100 101 func filterFilesOnly(paths []string) []string { 102 var files []string 103 for _, path := range paths { 104 info, err := os.Stat(path) 105 if err != nil { 106 continue 107 } 108 if info.IsDir() { 109 continue 110 } 111 files = append(files, path) 112 } 113 114 return files 115 }