// 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) } }