mesh/merge-gate head 423b24d4ee8b merged into trunk fcdecb436117 — fail: head 423b24d4ee8b does not merge cleanly into trunk fcdecb436117: in conflict cmd/…
mesh/repo-check head 423b24d4ee8b merged into trunk fcdecb436117 — fail: head 423b24d4ee8b does not merge cleanly into trunk fcdecb436117: in conflict cmd/…
mesh/delivery superseded: a newer head of the same pull request
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
2.9 KiB
Go
95 lines
2.9 KiB
Go
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
|
|
}
|