github.com/Benchkram/bob@v0.0.0-20220321080157-7c8f3876e225/bobtask/target/hash.go (about)

     1  package target
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/Benchkram/bob/pkg/dockermobyutil"
    13  	"github.com/Benchkram/bob/pkg/usererror"
    14  
    15  	"github.com/Benchkram/bob/pkg/file"
    16  	"github.com/Benchkram/bob/pkg/filehash"
    17  )
    18  
    19  // Hash creates a hash for the entire target
    20  func (t *T) Hash() (empty string, _ error) {
    21  	switch t.Type {
    22  	case Path:
    23  		return t.filepathHash()
    24  	case Docker:
    25  		return t.dockerImagesHash()
    26  	default:
    27  		return t.filepathHash()
    28  	}
    29  }
    30  
    31  func (t *T) filepathHash() (empty string, _ error) {
    32  	aggregatedHashes := bytes.NewBuffer([]byte{})
    33  	for _, f := range t.Paths {
    34  		target := filepath.Join(t.dir, f)
    35  
    36  		if !file.Exists(target) {
    37  			return empty, usererror.Wrapm(fmt.Errorf("target does not exist %q", f), "failed to hash target")
    38  		}
    39  		fi, err := os.Stat(target)
    40  		if err != nil {
    41  			return empty, fmt.Errorf("failed to get file info %q: %w", f, err)
    42  		}
    43  
    44  		if fi.IsDir() {
    45  			if err := filepath.WalkDir(target, func(p string, fi fs.DirEntry, err error) error {
    46  				if err != nil {
    47  					return err
    48  				}
    49  				if fi.IsDir() {
    50  					return nil
    51  				}
    52  
    53  				h, err := filehash.Hash(p)
    54  				if err != nil {
    55  					return fmt.Errorf("failed to hash target %q: %w", f, err)
    56  				}
    57  
    58  				_, err = aggregatedHashes.Write(h)
    59  				if err != nil {
    60  					return fmt.Errorf("failed to write target hash to aggregated hash %q: %w", f, err)
    61  				}
    62  
    63  				return nil
    64  			}); err != nil {
    65  				return empty, fmt.Errorf("failed to walk dir %q: %w", target, err)
    66  			}
    67  			// TODO: what happens on a empty dir?
    68  		} else {
    69  			h, err := filehash.Hash(target)
    70  			if err != nil {
    71  				return empty, fmt.Errorf("failed to hash target %q: %w", f, err)
    72  			}
    73  
    74  			_, err = aggregatedHashes.Write(h)
    75  			if err != nil {
    76  				return empty, fmt.Errorf("failed to write target hash to aggregated hash %q: %w", f, err)
    77  			}
    78  		}
    79  	}
    80  
    81  	h, err := filehash.HashBytes(aggregatedHashes)
    82  	if err != nil {
    83  		return empty, fmt.Errorf("failed to write aggregated target hash: %w", err)
    84  	}
    85  
    86  	return hex.EncodeToString(h), nil
    87  }
    88  
    89  func (t *T) dockerImagesHash() (string, error) {
    90  
    91  	var hash string
    92  
    93  	for _, image := range t.Paths {
    94  		h, err := t.dockerRegistryClient.ImageHash(image)
    95  		if err != nil {
    96  			if errors.Is(err, dockermobyutil.ErrImageNotFound) {
    97  				return "", usererror.Wrapm(err, "failed to fetch docker image hash")
    98  			} else {
    99  				return "", fmt.Errorf("failed to get docker image hash info %q: %w", image, err)
   100  			}
   101  		}
   102  		hash = hash + h
   103  
   104  	}
   105  
   106  	return hash, nil
   107  }