Merge main (upgrade safety's declared packages, issue 1840) and pin the R4e mesh-sdk member's final head 875c680d, re-vendored here and in modules/route-proxy; the controller announces declared-packages readable (issue 1790, D1; E)
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package buildstep
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// narrowKept are the files of a module's directory a narrowed build keeps beside its named paths: its manifest and
|
||||
// its lock files.
|
||||
var narrowKept = []string{"module.json", "package.json", "package-lock.json", "go.mod", "go.sum"}
|
||||
|
||||
// Narrow leaves in the fetched tree only what a build of an artifact that names its paths may see (novox/hq ADR 0499
|
||||
// clause 4): r.Dir's named paths, its manifest and lock files, and .git; everything else is removed, so a file outside
|
||||
// them cannot change what is built without changing its key. A named path absent at the tree is an error naming it,
|
||||
// and nothing is removed then. With no paths in r it changes nothing. Both build modules' fetch steps call it.
|
||||
func Narrow(tree string, r FetchRequest) error {
|
||||
if len(r.Paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
keep := map[string]bool{".git": true}
|
||||
for _, p := range r.Paths {
|
||||
rel := path.Join(r.Dir, p)
|
||||
if strings.HasPrefix(rel, "../") || rel == ".." || path.IsAbs(p) {
|
||||
return fmt.Errorf("the named path %q leaves the module's directory", p)
|
||||
}
|
||||
if _, err := os.Lstat(filepath.Join(tree, filepath.FromSlash(rel))); err != nil {
|
||||
return fmt.Errorf("the named path %q is not in the fetched tree: %w", p, err)
|
||||
}
|
||||
keep[rel] = true
|
||||
}
|
||||
for _, f := range narrowKept {
|
||||
keep[path.Join(r.Dir, f)] = true
|
||||
}
|
||||
return prune(tree, "", keep)
|
||||
}
|
||||
|
||||
// prune removes from dir (rel within the tree) every entry that is neither kept nor on the way to one.
|
||||
func prune(root, rel string, keep map[string]bool) error {
|
||||
entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(rel)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range entries {
|
||||
p := path.Join(rel, e.Name())
|
||||
if keep[p] {
|
||||
continue
|
||||
}
|
||||
ancestor := false
|
||||
for k := range keep {
|
||||
if strings.HasPrefix(k, p+"/") {
|
||||
ancestor = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ancestor && e.IsDir() {
|
||||
if err := prune(root, p, keep); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(root, filepath.FromSlash(p))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+20
-3
@@ -74,7 +74,10 @@ func (c *composer) overlayRange() string {
|
||||
}
|
||||
|
||||
// network builds the private network over the machines that resolved the module for it. A machine is on it because it
|
||||
// was given the module, or because it is joining with a key and an address (novox/hq ADR 0169).
|
||||
// was given the module, or because it is joining with a key and an address (novox/hq ADR 0169), or because its own set
|
||||
// could not be resolved and it holds a place (a key and an address): that place is its last good one on the network, and
|
||||
// it does not change because its modules failed, so every other machine keeps its path to it -- the hub above all -- and
|
||||
// only its own declaration is refused (issue 1833). A machine refused with no place is left off.
|
||||
func (c *composer) network(on map[string]bool, refused map[string]string) (*overlay.Generator, error) {
|
||||
tunnels := c.in.Tunnels
|
||||
carried := c.in.CarriedPeers
|
||||
@@ -85,9 +88,15 @@ func (c *composer) network(on map[string]bool, refused map[string]string) (*over
|
||||
var nodes []overlay.Node
|
||||
for _, node := range c.in.sorted() {
|
||||
p := node.Place
|
||||
if !on[node.Name] && !(isJoining[node.Name] && p.Key != "" && p.Address != "") {
|
||||
placed := p.Key != "" && p.Address != ""
|
||||
_, wasRefused := refused[node.Name]
|
||||
if !on[node.Name] && !((isJoining[node.Name] || wasRefused) && placed) {
|
||||
continue
|
||||
}
|
||||
if !on[node.Name] && wasRefused && !isJoining[node.Name] {
|
||||
c.say("%s could not be resolved and keeps its place on the private network at %s, so the others keep their path to it",
|
||||
node.Name, p.Address)
|
||||
}
|
||||
n := overlay.Node{
|
||||
Name: node.Name, Key: p.Key, Endpoint: p.Endpoint,
|
||||
Site: p.Site, Hub: p.Hub, Address: p.Address,
|
||||
@@ -233,7 +242,9 @@ func (c *composer) whereEveryoneIs() (map[string]string, error) {
|
||||
}
|
||||
|
||||
// onTheNetwork is every placed machine that resolves the private network: has an address AND runs what puts it there
|
||||
// (novox/hq issue 079). A machine that does not resolve is not counted, and said.
|
||||
// (novox/hq issue 079). A machine that does not resolve keeps its place when it holds one (a key and an address), by the
|
||||
// same rule as network(), so its name and its address stay where the tunnel to it does (issue 1833); one without is not
|
||||
// counted, and said.
|
||||
func (c *composer) onTheNetwork() []Node {
|
||||
var out []Node
|
||||
for _, n := range c.in.sorted() {
|
||||
@@ -247,6 +258,12 @@ func (c *composer) onTheNetwork() []Node {
|
||||
catalogue.Node{Name: n.Name, Site: n.Place.Site, Capabilities: n.Capabilities},
|
||||
catalogue.World{Unchecked: true, Holdings: c.in.Holdings})
|
||||
if err != nil {
|
||||
if n.Place.Key != "" {
|
||||
c.say("%s could not be resolved and keeps its place on the private network at %s, so the others keep their path to it",
|
||||
n.Name, n.Place.Address)
|
||||
out = append(out, n)
|
||||
continue
|
||||
}
|
||||
c.say("%s is not counted as on the network: it does not resolve: %v", n.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
+150
-1
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -27,6 +29,8 @@ type State struct {
|
||||
// kv binds this state's underlying key-value store: a live connection's own (Conn.State), or, in a
|
||||
// unit test, a fake that never reaches a bus.
|
||||
kv func(ctx context.Context) (jetstream.KeyValue, error)
|
||||
// closed says the connection is gone, so a watch ends; nil in a unit test.
|
||||
closed func() bool
|
||||
}
|
||||
|
||||
// bucket is this state's name on the bus.
|
||||
@@ -38,7 +42,7 @@ func (s State) bucket() string { return s.Module + "_" + s.Name }
|
||||
// not granted is refused there, promptly, as nats.ErrPermissionViolation.
|
||||
func (c *Conn) State(name string) State {
|
||||
bucket := c.account.Module + "_" + name
|
||||
return State{Module: c.account.Module, Name: name, kv: func(ctx context.Context) (jetstream.KeyValue, error) {
|
||||
return State{Module: c.account.Module, Name: name, closed: func() bool { return c.nc.IsClosed() }, kv: func(ctx context.Context) (jetstream.KeyValue, error) {
|
||||
if err := c.refusedUnannounced(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -131,3 +135,148 @@ func (s State) Update(key string, value any, revision uint64) (uint64, error) {
|
||||
}
|
||||
return rev, nil
|
||||
}
|
||||
|
||||
// Put writes a key whole, whatever it held, and returns the revision written.
|
||||
func (s State) Put(key string, value any) (uint64, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
||||
defer cancel()
|
||||
kv, err := s.store(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rev, err := kv.Put(ctx, key, data)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("%s's state %s, key %s: %w", s.Module, s.Name, key, err)
|
||||
}
|
||||
return rev, nil
|
||||
}
|
||||
|
||||
// Keys is every key with a current value, sorted; none is an empty list.
|
||||
func (s State) Keys() ([]string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
||||
defer cancel()
|
||||
kv, err := s.store(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := kv.Keys(ctx)
|
||||
if errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||
return []string{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s's state %s, keys: %w", s.Module, s.Name, err)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// watchPattern is a key narrowing as the bus names it: `*` stays one name, `**` is the rest, empty is every key.
|
||||
func watchPattern(key string) string {
|
||||
if key == "" {
|
||||
return jetstream.AllKeys
|
||||
}
|
||||
tokens := strings.Split(key, ".")
|
||||
for i, t := range tokens {
|
||||
if t == "**" {
|
||||
tokens[i] = ">"
|
||||
}
|
||||
}
|
||||
return strings.Join(tokens, ".")
|
||||
}
|
||||
|
||||
// Watch hands handle every current value — none that is deleted, each with Current set — and then every
|
||||
// change in order, and returns once the current values have all been handled. key narrows it: a key, with
|
||||
// `*` for one name and `**` for the rest; empty is every key. The changes go on for the life of the
|
||||
// connection; an error from handle, or the connection closing, ends them. Before the current values are
|
||||
// handled, either is returned as the error.
|
||||
func (s State) Watch(key string, handle func(stdio.StateChange) error) error {
|
||||
// the watch outlives this call, so its context is not the call's timeout: the connection ends it.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
bind, stopBind := context.WithTimeout(ctx, stateTimeout)
|
||||
kv, err := s.store(bind)
|
||||
stopBind()
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
w, err := kv.Watch(ctx, watchPattern(key))
|
||||
if err != nil {
|
||||
cancel()
|
||||
return fmt.Errorf("%s's state %s, watching %q: %w", s.Module, s.Name, key, err)
|
||||
}
|
||||
end := func() { w.Stop(); cancel() }
|
||||
next := func() (jetstream.KeyValueEntry, bool, error) {
|
||||
tick := time.NewTicker(250 * time.Millisecond)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case e, open := <-w.Updates():
|
||||
if !open {
|
||||
return nil, false, fmt.Errorf("%s's state %s: the watch ended", s.Module, s.Name)
|
||||
}
|
||||
return e, true, nil
|
||||
case <-tick.C:
|
||||
if s.closed != nil && s.closed() {
|
||||
return nil, false, fmt.Errorf("%s's state %s: the connection closed", s.Module, s.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
deliver := func(e jetstream.KeyValueEntry, current bool) error {
|
||||
c := stdio.StateChange{State: s.Name, Key: e.Key(), Op: "put", Revision: e.Revision(), Current: current}
|
||||
if e.Operation() == jetstream.KeyValuePut {
|
||||
c.Value = json.RawMessage(e.Value())
|
||||
} else {
|
||||
c.Op = "delete"
|
||||
}
|
||||
return handle(c)
|
||||
}
|
||||
for {
|
||||
e, _, err := next()
|
||||
if err != nil {
|
||||
end()
|
||||
return err
|
||||
}
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
if e.Operation() != jetstream.KeyValuePut {
|
||||
continue
|
||||
}
|
||||
if err := deliver(e, true); err != nil {
|
||||
end()
|
||||
return err
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
defer end()
|
||||
for {
|
||||
e, _, err := next()
|
||||
if err != nil || e == nil {
|
||||
return
|
||||
}
|
||||
if deliver(e, false) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a key. A key that was not there is not an error.
|
||||
func (s State) Delete(key string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), stateTimeout)
|
||||
defer cancel()
|
||||
kv, err := s.store(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kv.Delete(ctx, key); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||
return fmt.Errorf("%s's state %s, key %s: %w", s.Module, s.Name, key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261022161855-e76a5a31a07b
|
||||
# git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261022183749-875c680d9876
|
||||
## explicit; go 1.26.0
|
||||
git.novox.be/novox/mesh-sdk/go
|
||||
git.novox.be/novox/mesh-sdk/go/announce
|
||||
|
||||
Reference in New Issue
Block a user