github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bobtask/target/buildinfo.go (about) 1 package target 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "io/fs" 7 "os" 8 "path/filepath" 9 "sort" 10 11 "github.com/benchkram/bob/bobtask/buildinfo" 12 "github.com/benchkram/bob/pkg/file" 13 "github.com/benchkram/bob/pkg/filehash" 14 "github.com/benchkram/bob/pkg/usererror" 15 "github.com/benchkram/errz" 16 ) 17 18 // BuildInfo reads file info and computes the target hash 19 // for filesystem and docker targets. 20 func (t *T) BuildInfo() (bi *buildinfo.Targets, err error) { 21 defer errz.Recover(&err) 22 23 bi = buildinfo.NewTargets() 24 25 // Filesystem 26 buildInfoFilesystem, err := t.buildinfoFiles(t.filesystemEntriesRaw) 27 errz.Fatal(err) 28 bi.Filesystem = buildInfoFilesystem 29 30 // Docker 31 for _, image := range t.dockerImages { 32 hash, err := t.dockerImageHash(image) 33 errz.Fatal(err) 34 35 bi.Docker[image] = buildinfo.BuildInfoDocker{Hash: hash} 36 } 37 38 return bi, nil 39 } 40 41 func (t *T) buildinfoFiles(paths []string) (bi buildinfo.BuildInfoFiles, _ error) { 42 bi = *buildinfo.NewBuildInfoFiles() 43 44 h := filehash.New() 45 46 // Use a sorted path array to assure the hash of all files 47 // is computed in a consistent order. 48 sort.Strings(paths) 49 50 for _, path := range paths { 51 path = filepath.Join(t.dir, path) 52 53 if !file.Exists(path) { 54 return buildinfo.BuildInfoFiles{}, usererror.Wrapm(ErrTargetDoesNotExist, fmt.Sprintf("[path: %q]", path)) 55 } 56 targetInfo, err := os.Lstat(path) 57 if err != nil { 58 return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to get file info %q: %w", path, err) 59 } 60 61 if targetInfo.IsDir() { 62 if err := filepath.WalkDir(path, func(p string, f fs.DirEntry, err error) error { 63 if ShouldIgnore(p) { 64 return nil 65 } 66 if err != nil { 67 return err 68 } 69 if f.IsDir() { 70 // we add directories to the list of files to later verify target, but no need for size and hash 71 bi.Files[p] = buildinfo.BuildInfoFile{Size: -1, Hash: ""} 72 return nil 73 } 74 75 err = h.AddFile(p) 76 if err != nil { 77 return fmt.Errorf("failed to hash target %q: %w", f, err) 78 } 79 80 info, err := f.Info() 81 if err != nil { 82 return fmt.Errorf("failed to get file info %q: %w", p, err) 83 } 84 85 contentHash, err := filehash.HashOfFile(p) 86 if err != nil { 87 return fmt.Errorf("failed to get file hash %q: %w", p, err) 88 } 89 90 bi.Files[p] = buildinfo.BuildInfoFile{Size: info.Size(), Hash: contentHash} 91 92 return nil 93 }); err != nil { 94 return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to walk dir %q: %w", path, err) 95 } 96 // TODO: what happens on a empty dir? 97 } else { 98 if ShouldIgnore(path) { 99 continue 100 } 101 err = h.AddFile(path) 102 if err != nil { 103 return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to hash target %q: %w", path, err) 104 } 105 contentHash, err := filehash.HashOfFile(path) 106 if err != nil { 107 return buildinfo.BuildInfoFiles{}, fmt.Errorf("failed to get file hash %q: %w", path, err) 108 } 109 bi.Files[path] = buildinfo.BuildInfoFile{Size: targetInfo.Size(), Hash: contentHash} 110 } 111 } 112 113 bi.Hash = hex.EncodeToString(h.Sum()) 114 115 return bi, nil 116 }