package apply import ( "context" "fmt" "git.novox.be/novox/mesh-sdk/go/declaration" "github.com/novox/mesh-host/internal/store" "github.com/novox/mesh-host/internal/system" ) // WriteAlone writes one file a module's composition declares, through the apply's own file step, and records it as an // apply would; nothing else of the module is applied, and nothing that reads the file is moved (novox/hq ADR 0547 §8). // // **For a file whose reader takes the change itself.** The bus's user list is the one it is for: the bus module's // entrypoint watches the file and signals the server to reload in place, so the list is in force without a want of the // bus, which nobody writes (ADR 0539 §15), and without the container being touched. A file that a service or a container // must be moved to read is not written alone: it goes through the module's apply, which moves what reads it. // // The file is written only when its content differs from what is there (the file step's own rule), read back, and // recorded under the module whose resource it is, so the module's next apply finds it as its own and writes nothing. // The caller holds the apply's lock. log is said the path, the action and the digest, never the content. func WriteAlone(ctx context.Context, sys system.System, file *declaration.File, known store.State, run Runner, log func(string)) (Outcome, store.State, error) { if log == nil { log = func(string) {} } if file.Into != "" || file.CreateOnce || file.Sealed != "" || file.VaultSealed != nil || len(file.Secrets) > 0 { return Outcome{}, known, &Error{Resource: file.ID, Err: fmt.Errorf("%s is not a whole file of plain content, "+ "and only such a file is written alone; nothing was written", file.Path)} } if file.IsReference() { return Outcome{}, known, &Error{Resource: file.ID, Err: fmt.Errorf("%s is given by digest %s and was not "+ "resolved to its content; it is not written empty", file.Path, file.Digest)} } if held, isHeld := known.HeldAt(file.ID); isHeld { return Outcome{}, known, &Error{Resource: file.ID, Err: fmt.Errorf("%s is held as found for a module not "+ "yet taken (%s), so nothing was written", file.Path, held.Why)} } was, _ := known.Find(file.ID) if was.ID == "" { // Only over a file the mesh wrote: a file this node holds no record of is the module's apply's to take, with // the original kept, never this write's. A record of the path under another id (the declaration before the // switch named it so) is the one written to, so no second record of the file is left for an apply to remove. rec, ok := known.At(string(declaration.TypeFile), file.Path) if !ok { return Outcome{}, known, &Error{Resource: file.ID, Err: fmt.Errorf("this node holds no record of %s; it "+ "is written by the module's apply first, never alone", file.Path)} } renamed := *file renamed.ID = rec.ID file, was = &renamed, rec } in := inputs{declares: map[string]string{file.Identity(): declaredDigest(file)}, known: &known, groups: groupsWanted([]declaration.Resource{file}), handed: Handed{}, dirsBefore: map[string]bool{}, files: map[string]bool{file.Path: true}} outcome, err := applyOne(ctx, sys, file, run, map[string]bool{}, in, was, nil, nil) if err != nil { log(fmt.Sprintf(" failed %s (%s), alone: %v", file.ID, file.Path, err)) return outcome, known, &Error{Resource: file.ID, Err: err} } recordKeeping(&known, file, outcome, was) if moved(outcome) { log(fmt.Sprintf(" %s %s (%s), alone: %s", outcome.Action, file.ID, file.Path, outcome.wrote)) } return outcome, known, nil }