Files
distributor/internal/app/manifest.go

129 lines
3.6 KiB
Go

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
}