Files
mesh-host/internal/apply/references.go
T
2026-10-21 15:41:09 +02:00

96 lines
3.2 KiB
Go

package apply
import (
"context"
"fmt"
"os"
"path/filepath"
"unicode/utf8"
"encoding/base64"
"git.novox.be/novox/mesh-sdk/go/declaration"
)
// FetchObject reads the object of the declaration-files bucket named by name, no larger than limit bytes (novox/hq ADR
// 0486 §1, §2). Nil is a node-engine with no link to the bus: a file by reference is then only the file already there.
type FetchObject func(ctx context.Context, name string, limit int64) ([]byte, error)
// ResolveFileReferences puts the content of every file given by digest and size into the declaration, so what is
// applied afterwards is the declaration as it was before references existed (novox/hq ADR 0486 §1, issue 1695).
//
// **Nothing of the declaration is applied before every reference is resolved**: a missing object, one whose size or
// sha256 differs from what the signed declaration names, or a fetch that fails, refuses the declaration whole. The
// content is checked with File.Verify before it is kept, and is only ever written by the apply that follows.
//
// A file already on the machine whose content verifies is used as it is and fetches nothing: the reconcile of an
// unchanged declaration, and a node that is asleep and has no bus, apply as they always did. One object is fetched
// once however many files name its digest.
func ResolveFileReferences(ctx context.Context, d *declaration.Declaration, fetch FetchObject) error {
fetched := map[string][]byte{}
for i, r := range d.Resources {
f, ok := r.(*declaration.File)
if !ok || !f.IsReference() {
continue
}
body, err := referencedContent(ctx, f, fetch, fetched)
if err != nil {
return err
}
inline := *f
inline.Digest, inline.Size = "", 0
if utf8.Valid(body) {
inline.Content = string(body)
} else {
inline.Bytes = base64.StdEncoding.EncodeToString(body)
}
d.Resources[i] = &inline
}
return nil
}
func referencedContent(ctx context.Context, f *declaration.File, fetch FetchObject,
fetched map[string][]byte) ([]byte, error) {
if body, ok := fetched[f.Digest]; ok {
return body, nil
}
if body, ok := alreadyThere(f); ok {
fetched[f.Digest] = body
return body, nil
}
name, err := declaration.ObjectName(f.Digest)
if err != nil {
return nil, fmt.Errorf("file %s (%s): %w", f.ID, f.Path, err)
}
if fetch == nil {
return nil, fmt.Errorf("file %s (%s) is given by digest %s and is not on this machine, and this node-engine "+
"has no link to the bus to fetch it from", f.ID, f.Path, f.Digest)
}
body, err := fetch(ctx, name, f.Size)
if err != nil {
return nil, fmt.Errorf("file %s (%s): fetching the object %s of this node's own object bucket: %w",
f.ID, f.Path, name, err)
}
if err := f.Verify(body); err != nil {
return nil, fmt.Errorf("%s (%s): %w; nothing was applied", f.ID, f.Path, err)
}
fetched[f.Digest] = body
return body, nil
}
// alreadyThere is the content of the file on the machine when it is what the reference names.
func alreadyThere(f *declaration.File) ([]byte, bool) {
if !filepath.IsAbs(f.Path) {
return nil, false
}
info, err := os.Lstat(f.Path)
if err != nil || !info.Mode().IsRegular() || info.Size() != f.Size {
return nil, false
}
body, err := os.ReadFile(f.Path)
if err != nil || f.Verify(body) != nil {
return nil, false
}
return body, true
}