135 lines
5.2 KiB
Go
135 lines
5.2 KiB
Go
// Package takein is the one rule that turns a builder's done key into the manifest the mesh composes a module from
|
|
// (issue 1126): the module's manifest resolved with what its build made. It is mesh-controller's take-in of a done key
|
|
// (cmd/mesh-controller/key_takein.go builtOfKey, unpackTexts and Manifest.Resolve) moved into the SDK, so a node agent
|
|
// composing its own modules (novox/hq ADR 0547 §2) composes from the same built manifest the controller composes from:
|
|
// every artifact a resource names resolved to its reference and digest, a TypeScript bundle's launchers, and, for a
|
|
// moved module, each template's text read back from its templates archive and what its build keeps for the
|
|
// provisioner (catalogue.Handed). Composing from the manifest the indexer read instead hands no moved module and
|
|
// leaves every artifact unbuilt.
|
|
//
|
|
// It reads nothing itself: the archive is read through the caller's Archive, from wherever the caller reaches the
|
|
// artifact store.
|
|
package takein
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Archive reads the archive a recorded reference names (catalogue.Recorded: the artifact store's address left out),
|
|
// checked against its digest.
|
|
type Archive func(ctx context.Context, reference, digest string) ([]byte, error)
|
|
|
|
// Manifest is m, as its manifest at b's fingerprint reads, resolved with what b made: the built manifest. b must be done
|
|
// and every artifact must name its reference.
|
|
// A templated module's templates archive is read through archive; a module without templates reads none, and archive
|
|
// may then be nil.
|
|
func Manifest(ctx context.Context, m catalogue.Manifest, b delivery.BuilderBuild, archive Archive) (catalogue.Manifest, error) {
|
|
if b.State != delivery.BuildDone {
|
|
return catalogue.Manifest{}, fmt.Errorf("takein: the build of %s is %s, not done: nothing to resolve it with", m.Module, b.State)
|
|
}
|
|
// **A done key whose artifact names no reference is waited for, never composed from** (novox/hq ADR 0498 §8), in the
|
|
// controller's words (mesh-controller internal/builderkeys Key.Says): a key done before R4e, or a candidate's image or
|
|
// package not yet built again for the trunk.
|
|
var missing []string
|
|
for _, a := range b.Artifacts {
|
|
if strings.TrimSpace(a.Reference) == "" {
|
|
missing = append(missing, a.Name)
|
|
}
|
|
}
|
|
if len(missing) > 0 {
|
|
return catalogue.Manifest{}, fmt.Errorf("takein: the build of %s is done and waits for the reference of %s",
|
|
m.Module, strings.Join(missing, ", "))
|
|
}
|
|
built, err := Built(ctx, m, b.Artifacts, archive)
|
|
if err != nil {
|
|
return catalogue.Manifest{}, err
|
|
}
|
|
resolved, err := m.Resolve(built)
|
|
if err != nil {
|
|
return catalogue.Manifest{}, fmt.Errorf("takein: the build of %s does not resolve: %w", m.Module, err)
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
// Built is each of a done key's artifacts as catalogue.Manifest.Resolve reads it: its recorded reference and digest, a
|
|
// TypeScript bundle's launchers by the build modules' one rule (catalogue.Launchers), and, for the archive a module packs
|
|
// its templates into, each template's text read back from that archive.
|
|
func Built(ctx context.Context, m catalogue.Manifest, artifacts []delivery.BuiltArtifact, archive Archive) ([]catalogue.Built, error) {
|
|
declared := map[string]catalogue.Artifact{}
|
|
if m.Build != nil {
|
|
for _, a := range m.Build.Artifacts {
|
|
declared[a.Name] = a
|
|
}
|
|
}
|
|
templates := ""
|
|
if m.Templated() {
|
|
templates = catalogue.TemplatesArtifact(m)
|
|
}
|
|
var out []catalogue.Built
|
|
for _, a := range artifacts {
|
|
d := declared[a.Name]
|
|
kind := a.Kind
|
|
if kind == "" {
|
|
kind = d.Kind
|
|
}
|
|
b := catalogue.Built{Name: a.Name, Kind: kind, Reference: catalogue.Recorded(a.Reference), Digest: a.Digest}
|
|
if launchers := catalogue.Launchers(d.Language, d.Entrypoints); len(launchers) > 0 {
|
|
b.Launchers = launchers
|
|
}
|
|
if a.Name == templates && templates != "" {
|
|
if archive == nil {
|
|
return nil, fmt.Errorf("takein: %s packs its templates into %q, and nothing here reads the artifact store",
|
|
m.Module, a.Name)
|
|
}
|
|
body, err := archive(ctx, b.Reference, b.Digest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("takein: the templates archive %q of %s cannot be read from the artifact store: %w",
|
|
a.Name, m.Module, err)
|
|
}
|
|
if b.Texts, err = UnpackTexts(body); err != nil {
|
|
return nil, fmt.Errorf("takein: the templates archive %q of %s cannot be read back: %w", a.Name, m.Module, err)
|
|
}
|
|
}
|
|
out = append(out, b)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// UnpackTexts is every regular file of a packed templates archive (gzip over tar), 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(io.LimitReader(reader, 16<<20))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
texts[strings.TrimPrefix(header.Name, "./")] = string(text)
|
|
}
|
|
}
|