119 lines
2.8 KiB
Go
119 lines
2.8 KiB
Go
package bundle
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
func BuildManifest(opts BuildOptions) (Manifest, error) {
|
|
if opts.Root == "" {
|
|
return Manifest{}, fmt.Errorf("root is required")
|
|
}
|
|
if opts.ID == "" {
|
|
return Manifest{}, fmt.Errorf("id is required")
|
|
}
|
|
explicit := len(opts.Files) > 0
|
|
if explicit == opts.Scan {
|
|
return Manifest{}, fmt.Errorf("select exactly one file mode")
|
|
}
|
|
created := opts.Created
|
|
if created.IsZero() {
|
|
created = time.Now().UTC()
|
|
}
|
|
|
|
paths := append([]string(nil), opts.Files...)
|
|
var err error
|
|
if opts.Scan {
|
|
paths, err = scanSourcePaths(opts.Root)
|
|
if err != nil {
|
|
return Manifest{}, err
|
|
}
|
|
}
|
|
files := make([]ManifestFile, 0, len(paths))
|
|
for _, sourcePath := range paths {
|
|
file, err := buildManifestFile(opts.Root, sourcePath)
|
|
if err != nil {
|
|
return Manifest{}, err
|
|
}
|
|
files = append(files, file)
|
|
}
|
|
manifest := Manifest{
|
|
SchemaVersion: SchemaVersion,
|
|
ID: opts.ID,
|
|
Created: created,
|
|
Files: files,
|
|
}
|
|
manifest.Digest = BundleDigest(manifest.Files)
|
|
if err := ValidateManifest(manifest); err != nil {
|
|
return Manifest{}, err
|
|
}
|
|
return manifest, nil
|
|
}
|
|
|
|
func scanSourcePaths(root string) ([]string, error) {
|
|
var paths []string
|
|
err := filepath.WalkDir(root, func(filePath string, entry os.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if filePath == root {
|
|
return nil
|
|
}
|
|
relative, err := filepath.Rel(root, filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sourcePath := filepath.ToSlash(relative)
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
return fmt.Errorf("source file %q must be a regular file", sourcePath)
|
|
}
|
|
if entry.IsDir() {
|
|
return nil
|
|
}
|
|
if path.Base(sourcePath) == ManifestName || path.Base(sourcePath) == distributorStateName {
|
|
return nil
|
|
}
|
|
info, err := entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return fmt.Errorf("source file %q must be a regular file", sourcePath)
|
|
}
|
|
paths = append(paths, sourcePath)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Strings(paths)
|
|
return paths, nil
|
|
}
|
|
|
|
func buildManifestFile(root, sourcePath string) (ManifestFile, error) {
|
|
if err := ValidateSourcePath(sourcePath); err != nil {
|
|
return ManifestFile{}, fmt.Errorf("source file %q: %w", sourcePath, err)
|
|
}
|
|
fullPath := filepath.Join(root, filepath.FromSlash(sourcePath))
|
|
info, err := os.Lstat(fullPath)
|
|
if err != nil {
|
|
return ManifestFile{}, fmt.Errorf("source file %q stat: %w", sourcePath, err)
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return ManifestFile{}, fmt.Errorf("source file %q must be a regular file", sourcePath)
|
|
}
|
|
data, err := os.ReadFile(fullPath)
|
|
if err != nil {
|
|
return ManifestFile{}, fmt.Errorf("source file %q read: %w", sourcePath, err)
|
|
}
|
|
return ManifestFile{
|
|
Path: sourcePath,
|
|
SHA256: FileDigest(data),
|
|
Size: int64(len(data)),
|
|
}, nil
|
|
}
|