package broker import ( "context" "fmt" "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/nats-io/nats.go/jetstream" ) // Release 6's grants, carried ahead of it (novox/hq ADR 0539 clauses 2, 6, 9 and 16; issue 1945, the review's A1). The // switch to release 6 is the last delivery plan of today's controller, and the bus's user list after it is the one // today's controller composes: so the subjects the new controller, every new node-engine and the assigner use after the // switch are granted by this controller already, before the group merges. Unused until then: nothing writes or reads // these buckets before release 6. // // Names as mesh-sdk's release 6 gives them (delivery.ComposedBucketFor, ControllerComposedBucket); written out here // because this controller's mesh-sdk predates them, and held equal by release 6's own tests. // ComposedBucketPrefix starts a node's bucket of compositions, `composed-`. const ComposedBucketPrefix = "composed-" // ControllerComposed is the local name of the compositions' digests, `mesh-controller_composed`, which the assigner reads. const ControllerComposed = "composed" // ControllerComposedBucket is the bucket of the compositions' digests. var ControllerComposedBucket = BucketName(ControllerSeat, ControllerComposed) // composedBucketOf is a node's bucket of compositions; a node name a subject would widen is refused. func composedBucketOf(node string) (string, error) { if !safeSubject.MatchString(node) { return "", fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node) } return ComposedBucketPrefix + node, nil } // ComposedWrites is what the controller publishes to write a node's compositions: the bucket's own subjects. func ComposedWrites(node string) (string, error) { bucket, err := composedBucketOf(node) if err != nil { return "", err } return "$KV." + bucket + ".>", nil } // ComposedReads is what a node's node-engine publishes to follow its wants and compositions: every key of its own // composition bucket, and its own wants (`want..`, `want..node`) in the assigner's bucket; nothing // of another node's, nor the digests. func ComposedReads(node string) ([]string, error) { bucket, err := composedBucketOf(node) if err != nil { return nil, err } return append(readGrants(bucket, ""), readGrants(delivery.AssignerWantsBucket, "want."+node+".>")...), nil } // VerbsTheNodeAgentAsks are the seat verbs a node's node-engine calls of its own machine while it applies a want: its // backup holder backs a data module up before it moves, and says when it was (ADR 0539 clause 16). var VerbsTheNodeAgentAsks = []SeatVerb{{Seat: "node-backup", Verb: "now"}, {Seat: "node-backup", Verb: "backed-up"}} // NodeAgentAsks are the subjects of VerbsTheNodeAgentAsks for one node: `mesh.seat..tool..`. func NodeAgentAsks(node string) ([]string, error) { if !safeSubject.MatchString(node) { return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node) } var out []string for _, v := range VerbsTheNodeAgentAsks { out = append(out, "mesh.seat."+v.Seat+".tool."+v.Verb+"."+node) } return out, nil } // NodeAgentHears are the events a node's node-engine subscribes while it applies a want: the backup holder's word that a // backup is taken, from whichever module holds node-backup on its machine. var NodeAgentHears = []string{"mesh.mod." + BackupHolderModule + ".event.backed-up"} // BackupHolderModule is the module that holds node-backup on every node, restic: the one whose word a node-engine hears. // A second holder of the seat is a change of this grant. const BackupHolderModule = "restic" // ComposedDigestsConfig is the bucket of the compositions' digests as the controller asserts it: five revisions a key. func ComposedDigestsConfig() jetstream.KeyValueConfig { return jetstream.KeyValueConfig{ Bucket: ControllerComposedBucket, Description: "each composition's digest and the judgement of its move from what the node runs (novox/hq ADR 0539 " + "clauses 2 and 14): written by the controller alone, read by the assigner; empty until release 6", History: 5, MaxValueSize: 4 << 10, MaxBytes: 64 << 20, Storage: jetstream.FileStorage, } } // EnsureComposedDigests asserts the bucket of the compositions' digests. func EnsureComposedDigests(ctx context.Context, js jetstream.JetStream) error { if _, err := js.CreateOrUpdateKeyValue(ctx, ComposedDigestsConfig()); err != nil { return fmt.Errorf("asserting bucket %s: %w", ControllerComposedBucket, err) } return nil } // administers says a person may invoke every tool (`*`): the operator. func administers(invokes []string) bool { for _, i := range invokes { if i == "*" { return true } } return false } // HandPathWrites is what the operator's account publishes for release 6's hand path (ADR 0539 clauses 2 and 10): a want // of any node, written and read back, in the assigner's bucket; each named node's compositions; the digests; and each // node's object bucket, for a file over 64 KiB. func HandPathWrites(nodes []string) ([]string, error) { out := append([]string{"$KV." + delivery.AssignerWantsBucket + ".want.>"}, readGrants(delivery.AssignerWantsBucket, "want.>")...) out = append(out, "$KV."+ControllerComposedBucket+".>") out = append(out, readGrants(ControllerComposedBucket, "")...) for _, node := range nodes { w, err := ComposedWrites(node) if err != nil { return nil, err } bucket, _ := composedBucketOf(node) out = append(out, w) out = append(out, readGrants(bucket, "")...) files, err := DeclarationFilesWrites(node) if err != nil { return nil, err } stream := "OBJ_" + DeclarationFilesBucket + node // The object store's put reads an object's metadata already there and, replacing it, purges its old chunks. out = append(out, files, "$JS.API.STREAM.INFO."+stream, "$JS.API.DIRECT.GET."+stream+".>", "$JS.API.STREAM.MSG.GET."+stream, "$JS.API.STREAM.PURGE."+stream) } return out, nil }