package bundle import ( "context" "fmt" "path" "sort" "strings" "gitea.maximumdirect.net/eric/distributor/internal/storage" ) func Discover(ctx context.Context, backend storage.Backend, sourceRoot string) ([]Bundle, error) { if err := storage.ValidatePrefix(sourceRoot); err != nil { return nil, err } entries, err := storage.List(ctx, backend, sourceRoot, storage.WalkOptions{Recursive: true}) if err != nil { return nil, err } var roots []string for _, entry := range entries { if entry.Type == storage.EntryTypeDirectory { continue } if path.Base(entry.Path) == ManifestName { roots = append(roots, path.Dir(entry.Path)) } } for index, root := range roots { if root == "." { roots[index] = "" } } sort.Strings(roots) if len(roots) == 0 { return nil, fmt.Errorf("no bundles found under %q", storage.DisplayPath(sourceRoot)) } if err := rejectNestedRoots(roots); err != nil { return nil, err } bundles := make([]Bundle, 0, len(roots)) for _, root := range roots { relativeRoot := relativeToSource(sourceRoot, root) sourceBundle, err := validateAt(ctx, backend, root, relativeRoot) if err != nil { return nil, err } bundles = append(bundles, sourceBundle) } return bundles, nil } func rejectNestedRoots(roots []string) error { for index, root := range roots { for _, candidate := range roots[index+1:] { if isAncestor(root, candidate) { return fmt.Errorf("nested manifest %q under bundle %q", storage.DisplayPath(candidate), storage.DisplayPath(root)) } } } return nil } func isAncestor(root, candidate string) bool { if root == "" { return candidate != "" } return strings.HasPrefix(candidate, root+"/") } func relativeToSource(sourceRoot, bundleRoot string) string { if sourceRoot == "" { return bundleRoot } if bundleRoot == sourceRoot { return "" } return strings.TrimPrefix(bundleRoot, sourceRoot+"/") }