124 lines
3.9 KiB
Go
124 lines
3.9 KiB
Go
package bundle
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.maximumdirect.net/eric/distributor/internal/storage"
|
|
)
|
|
|
|
func ValidateSourcePath(path string) error {
|
|
if err := storage.ValidatePath(path); err != nil {
|
|
return err
|
|
}
|
|
switch path {
|
|
case ManifestName, storage.StateFileName:
|
|
return fmt.Errorf("%q is reserved", path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateManifest(manifest Manifest) error {
|
|
if manifest.SchemaVersion != 1 {
|
|
return fmt.Errorf("schema_version must be 1")
|
|
}
|
|
if manifest.ID == "" {
|
|
return fmt.Errorf("id is required")
|
|
}
|
|
if err := ValidateDigest(manifest.Digest); err != nil {
|
|
return fmt.Errorf("digest: %w", err)
|
|
}
|
|
if manifest.Created.IsZero() {
|
|
return fmt.Errorf("created is required")
|
|
}
|
|
if len(manifest.Files) == 0 {
|
|
return fmt.Errorf("files is required")
|
|
}
|
|
seen := make(map[string]struct{}, len(manifest.Files))
|
|
for index, file := range manifest.Files {
|
|
if err := ValidateSourcePath(file.Path); err != nil {
|
|
return fmt.Errorf("files[%d].path: %w", index, err)
|
|
}
|
|
if err := ValidateDigest(file.SHA256); err != nil {
|
|
return fmt.Errorf("files[%d].sha256: %w", index, err)
|
|
}
|
|
if file.Size < 0 {
|
|
return fmt.Errorf("files[%d].size must be non-negative", index)
|
|
}
|
|
if _, exists := seen[file.Path]; exists {
|
|
return fmt.Errorf("files[%d].path duplicates %q", index, file.Path)
|
|
}
|
|
seen[file.Path] = struct{}{}
|
|
}
|
|
if actual := BundleDigest(manifest.Files); actual != manifest.Digest {
|
|
return fmt.Errorf("digest mismatch: got %s want %s", actual, manifest.Digest)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Validate(ctx context.Context, backend storage.Backend, bundleRoot string) (Bundle, error) {
|
|
return validateAt(ctx, backend, bundleRoot, bundleRoot)
|
|
}
|
|
|
|
func validateAt(ctx context.Context, backend storage.Backend, bundleRoot, relativeRoot string) (Bundle, error) {
|
|
if err := storage.ValidatePrefix(bundleRoot); err != nil {
|
|
return Bundle{}, err
|
|
}
|
|
manifestPath, err := storage.Join(bundleRoot, ManifestName)
|
|
if err != nil {
|
|
return Bundle{}, err
|
|
}
|
|
manifestData, err := backend.ReadFile(ctx, manifestPath)
|
|
if err != nil {
|
|
return Bundle{}, fmt.Errorf("read manifest %q: %w", manifestPath, err)
|
|
}
|
|
manifest, err := ParseManifest(manifestData)
|
|
if err != nil {
|
|
return Bundle{}, fmt.Errorf("bundle %q: %w", displayRoot(relativeRoot), err)
|
|
}
|
|
|
|
for index, manifestFile := range manifest.Files {
|
|
filePath, err := storage.Join(bundleRoot, manifestFile.Path)
|
|
if err != nil {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q: %w", displayRoot(relativeRoot), manifestFile.Path, err)
|
|
}
|
|
entry, err := backend.Stat(ctx, filePath)
|
|
if err != nil {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q stat: %w", displayRoot(relativeRoot), manifestFile.Path, err)
|
|
}
|
|
if entry.Type != storage.EntryTypeFile {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q must be a regular file", displayRoot(relativeRoot), manifestFile.Path)
|
|
}
|
|
if entry.Size != manifestFile.Size {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q size mismatch: got %d want %d", displayRoot(relativeRoot), manifestFile.Path, entry.Size, manifestFile.Size)
|
|
}
|
|
data, err := backend.ReadFile(ctx, filePath)
|
|
if err != nil {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q read: %w", displayRoot(relativeRoot), manifestFile.Path, err)
|
|
}
|
|
actualDigest := FileDigest(data)
|
|
if actualDigest != manifestFile.SHA256 {
|
|
return Bundle{}, fmt.Errorf("bundle %q file %q sha256 mismatch: got %s want %s", displayRoot(relativeRoot), manifestFile.Path, actualDigest, manifestFile.SHA256)
|
|
}
|
|
manifest.Files[index].SHA256 = actualDigest
|
|
manifest.Files[index].Size = int64(len(data))
|
|
}
|
|
|
|
actualBundleDigest := BundleDigest(manifest.Files)
|
|
if actualBundleDigest != manifest.Digest {
|
|
return Bundle{}, fmt.Errorf("bundle %q digest mismatch: got %s want %s", displayRoot(relativeRoot), actualBundleDigest, manifest.Digest)
|
|
}
|
|
|
|
return Bundle{
|
|
RootRelativePath: relativeRoot,
|
|
Manifest: manifest,
|
|
}, nil
|
|
}
|
|
|
|
func displayRoot(root string) string {
|
|
if root == "" {
|
|
return "."
|
|
}
|
|
return root
|
|
}
|