package builder import ( "archive/tar" "bytes" "compress/gzip" "crypto/sha256" "encoding/hex" "errors" "fmt" "io" "path/filepath" "github.com/novox/mesh-controller/internal/catalogue" ) // withTemplateTexts is what was built with each template's text, for a module whose files name templates // (novox/hq ADR 0349 §2): its templates archive gains the text of every file in it, read back from the packed // bytes, by its path inside the archive. Resolve reads them into the files' content. // // **Read from the archive, proven by its digest.** The module's files/ is packed again, as the build packs it, // and the bytes are read only when they hash to the digest the build made or took: a fresh build's own archive, // or a check's build a walk copied by digest without building (novox/hq ADR 0350 §2). Packing is deterministic, // so one source packs to one digest; a tree that packs to another is not the archive, and its text is refused. func withTemplateTexts(manifest catalogue.Manifest, within string, built []catalogue.Built) ([]catalogue.Built, error) { if !manifest.Templated() { return built, nil } name := catalogue.TemplatesArtifact(manifest) at := -1 for i, b := range built { if b.Name == name { at = i } } if name == "" || at < 0 { return nil, fmt.Errorf("%s names templates and built no archive of %s/ to read them from", manifest.Module, catalogue.TemplatesDir) } body, err := pack(filepath.Join(within, catalogue.TemplatesDir)) if err != nil { return nil, fmt.Errorf("%s: packing %s/ to read its templates back failed: %w", manifest.Module, catalogue.TemplatesDir, err) } sum := sha256.Sum256(body) digest := "sha256:" + hex.EncodeToString(sum[:]) if built[at].Digest != digest { return nil, fmt.Errorf("%s: %s/ packs to the digest %s, and the archive %q built or taken is %s: the "+ "templates are read only from the archive the mesh records", manifest.Module, catalogue.TemplatesDir, digest, name, orNone(built[at].Digest)) } texts, err := unpackTexts(body) if err != nil { return nil, fmt.Errorf("%s: reading the archive %q back failed: %w", manifest.Module, name, err) } out := append([]catalogue.Built(nil), built...) out[at].Texts = texts return out, nil } // unpackTexts is every file of a packed archive (pack), by its path inside it. func unpackTexts(body []byte) (map[string]string, error) { zipped, err := gzip.NewReader(bytes.NewReader(body)) if err != nil { return nil, err } defer zipped.Close() reader := tar.NewReader(zipped) texts := map[string]string{} for { header, err := reader.Next() if errors.Is(err, io.EOF) { return texts, nil } if err != nil { return nil, err } if header.Typeflag != tar.TypeReg { continue } text, err := io.ReadAll(reader) if err != nil { return nil, err } texts[header.Name] = string(text) } } func orNone(digest string) string { if digest == "" { return "no digest" } return digest }