Add public bundle manifest package
This commit is contained in:
118
pkg/bundle/build.go
Normal file
118
pkg/bundle/build.go
Normal file
@@ -0,0 +1,118 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user