package app import ( "context" "fmt" "io" "path/filepath" "strings" "time" producerbundle "gitea.maximumdirect.net/eric/distributor/pkg/bundle" ) type ManifestCreateOptions struct { Root string ID string Created string Files []string Overwrite bool Stdout io.Writer OutputFormat OutputFormat } func ManifestCreate(ctx context.Context, options ManifestCreateOptions) error { if err := ValidateOutputFormat(options.OutputFormat); err != nil { return err } if err := ctx.Err(); err != nil { return err } if options.Root == "" { return fmt.Errorf("manifest create command requires a bundle path") } if options.ID == "" { return fmt.Errorf("manifest create command requires --id") } created, err := parseOptionalCreated(options.Created) if err != nil { return err } files := normalizeManifestFiles(options.Files) buildOptions := producerbundle.BuildOptions{ Root: options.Root, ID: options.ID, Created: created, Files: files, Scan: len(files) == 0, } manifest, err := producerbundle.BuildManifest(buildOptions) if err != nil { return err } if err := producerbundle.WriteManifest(options.Root, manifest, producerbundle.WriteManifestOptions{Overwrite: options.Overwrite}); err != nil { return err } loaded, err := producerbundle.LoadManifest(options.Root) if err != nil { return err } if err := producerbundle.ValidateBundle(options.Root, loaded); err != nil { return err } result := manifestCreateResultFromManifest(options.Root, loaded) if IsJSONOutput(options.OutputFormat) { return WriteJSONEnvelope(options.Stdout, "manifest create", true, nil, result, nil) } if options.Stdout != nil { _, err = fmt.Fprintf(options.Stdout, "created %s\nbundle: %s\nfiles: %d\ndigest: %s\n", producerbundle.ManifestName, result.ID, result.FileCount, result.Digest) } return err } func parseOptionalCreated(value string) (time.Time, error) { if value == "" { return time.Time{}, nil } created, err := time.Parse(time.RFC3339, value) if err != nil { return time.Time{}, fmt.Errorf("created must be RFC3339: %w", err) } return created, nil } func normalizeManifestFiles(files []string) []string { normalized := make([]string, 0, len(files)) for _, file := range files { normalized = append(normalized, filepath.ToSlash(filepath.Clean(strings.ReplaceAll(file, "\\", string(filepath.Separator))))) } return normalized } type manifestCreateResult struct { ManifestPath string `json:"manifest_path"` Root string `json:"root"` ID string `json:"id"` Created string `json:"created"` Digest string `json:"digest"` FileCount int `json:"file_count"` Files []manifestCreateFileResult `json:"files"` } type manifestCreateFileResult struct { Path string `json:"path"` SHA256 string `json:"sha256"` Size int64 `json:"size"` } func manifestCreateResultFromManifest(root string, manifest producerbundle.Manifest) manifestCreateResult { result := manifestCreateResult{ ManifestPath: filepath.ToSlash(filepath.Join(root, producerbundle.ManifestName)), Root: filepath.ToSlash(root), ID: manifest.ID, Created: manifest.Created.Format(time.RFC3339), Digest: manifest.Digest, FileCount: len(manifest.Files), Files: make([]manifestCreateFileResult, 0, len(manifest.Files)), } for _, file := range manifest.Files { result.Files = append(result.Files, manifestCreateFileResult{ Path: file.Path, SHA256: file.SHA256, Size: file.Size, }) } return result }