Files
distributor/pkg/bundle/build.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
}