Count the bus's users from the list a digest names, take only a composition of this node and module, and refuse a list that would lock the mesh out (review of the bridge, ADR 0547 §8)
mesh/delivery merged: source.main moved to novox/mesh-host@d75c66dc7e27
mesh/delivery-group group fix/1945-user-list-follows merged: every member is merged
mesh/check pass 5e71ab6ae612 on d75c66dc7e27 | unit 303 run, 1077 kept, 5 skipped | integration 306 run, 1079 kept, 4 skipped | over bound: unit, inte…
mesh/delivery merged: source.main moved to novox/mesh-host@d75c66dc7e27
mesh/delivery-group group fix/1945-user-list-follows merged: every member is merged
mesh/check pass 5e71ab6ae612 on d75c66dc7e27 | unit 303 run, 1077 kept, 5 skipped | integration 306 run, 1079 kept, 4 skipped | over bound: unit, inte…
B1: the controller sends the user list by digest (over 64 KiB), so its users are counted from the list as resolved, not from the composition's inline content, which is empty then. R1: a composition signed for another node or module than its key names is no source. R2: a list without the controller's own user, or one that drops more than half the users of the file on the machine, is not written; the have says why and the file on the machine is kept.
This commit is contained in:
+50
-12
@@ -28,8 +28,12 @@ func userListFollower(opts options, session *link.Session, composedBucket string
|
||||
Node: session.Membership.Node,
|
||||
Composed: bucketWatch{session, composedBucket},
|
||||
Trusted: trusted,
|
||||
Write: func(ctx context.Context, module string, raw json.RawMessage) (string, bool, error) {
|
||||
return writeUserListAlone(session.WithLink(ctx), opts, raw, say)
|
||||
Resolve: func(ctx context.Context, raw json.RawMessage) ([]byte, error) {
|
||||
return resolveUserList(session.WithLink(ctx), raw)
|
||||
},
|
||||
OnDisk: userListOnDisk,
|
||||
Write: func(ctx context.Context, module string, raw json.RawMessage, body []byte) (string, bool, error) {
|
||||
return writeUserListAlone(session.WithLink(ctx), opts, raw, body, say)
|
||||
},
|
||||
Report: func(ctx context.Context, module string, w userlist.Written) {
|
||||
session.NoteUserList(ctx, module, w)
|
||||
@@ -38,21 +42,55 @@ func userListFollower(opts options, session *link.Session, composedBucket string
|
||||
})
|
||||
}
|
||||
|
||||
// writeUserListAlone writes one user-list resource alone (apply.WriteAlone): its content had by digest when it is
|
||||
// given so, the node's state read and saved under the apply's locks, and the digest of the file on the machine after.
|
||||
func writeUserListAlone(ctx context.Context, opts options, raw []byte, say link.Announce) (string, bool, error) {
|
||||
// userListFile is a user-list resource as the file it declares.
|
||||
func userListFile(raw []byte) (*declaration.File, error) {
|
||||
r, err := declaration.ParseResource(raw)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("the user list is not a resource: %w", err)
|
||||
return nil, fmt.Errorf("the user list is not a resource: %w", err)
|
||||
}
|
||||
if _, ok := r.(*declaration.File); !ok {
|
||||
return "", false, fmt.Errorf("the user list is a %s, not a file", r.Kind())
|
||||
f, ok := r.(*declaration.File)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("the user list is a %s, not a file", r.Kind())
|
||||
}
|
||||
d := &declaration.Declaration{Version: declaration.Version, Resources: []declaration.Resource{r}}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// resolveUserList is the list a user-list resource holds: its content, or the object its digest names in this node's
|
||||
// file bucket, fetched and verified (apply.ResolveFileReferences, ADR 0486 §1).
|
||||
func resolveUserList(ctx context.Context, raw []byte) ([]byte, error) {
|
||||
f, err := userListFile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &declaration.Declaration{Version: declaration.Version, Resources: []declaration.Resource{f}}
|
||||
if err := apply.ResolveFileReferences(ctx, d, link.FilesFrom(ctx)); err != nil {
|
||||
return "", false, fmt.Errorf("the user list could not be had by its digest: %w", err)
|
||||
return nil, fmt.Errorf("the user list could not be had by its digest: %w", err)
|
||||
}
|
||||
file := d.Resources[0].(*declaration.File)
|
||||
return []byte(d.Resources[0].(*declaration.File).Content), nil
|
||||
}
|
||||
|
||||
// userListOnDisk is what the user list's file holds on this machine now; nil when there is none.
|
||||
func userListOnDisk(raw json.RawMessage) ([]byte, error) {
|
||||
f, err := userListFile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := os.ReadFile(f.Path)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// writeUserListAlone writes one user-list resource alone (apply.WriteAlone) with body as its content, the node's state
|
||||
// read and saved under the apply's locks, and answers the digest of the file on the machine after.
|
||||
func writeUserListAlone(ctx context.Context, opts options, raw []byte, body []byte, say link.Announce) (string, bool, error) {
|
||||
declared, err := userListFile(raw)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
file := *declared
|
||||
file.Content, file.Digest, file.Size = string(body), "", 0
|
||||
|
||||
applying.Lock()
|
||||
defer applying.Unlock()
|
||||
@@ -69,7 +107,7 @@ func writeUserListAlone(ctx context.Context, opts options, raw []byte, say link.
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
out, updated, writeErr := apply.WriteAlone(ctx, built, file, known, apply.ExecRunner, announceOr(say))
|
||||
out, updated, writeErr := apply.WriteAlone(ctx, built, &file, known, apply.ExecRunner, announceOr(say))
|
||||
// Saved whichever way it went, as an apply's: what was written is on the machine either way.
|
||||
if err := store.Save(opts.state, updated); err != nil {
|
||||
writeErr = errors.Join(writeErr, errors.New("written, and the node's state could not be saved: "+err.Error()))
|
||||
|
||||
Reference in New Issue
Block a user