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  }