|
|
|
@@ -262,134 +262,6 @@ func certificateFor(ctx context.Context, open *stores, node string) (string, str
|
|
|
|
|
return issued, authority.Certificate, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// grantsFor is every credential this node must create, because something elsewhere uses it.
|
|
|
|
|
//
|
|
|
|
|
// The mirror of what a consumer is given, and the half that makes the credential real: a password
|
|
|
|
|
// nothing was told to create is a password that authenticates nowhere. Sealed to this node, so
|
|
|
|
|
// the mesh hands over something it cannot itself use.
|
|
|
|
|
//
|
|
|
|
|
// **One consumer's identity never refuses the provider's machine** (novox/hq ADR 0225, issue 263).
|
|
|
|
|
// A consumer whose identity overflows the provision's bound is left out of the grants and returned
|
|
|
|
|
// beside them, for push, plan and `status` to say; every other consumer is granted and the provider's
|
|
|
|
|
// declaration composes. Refusing here once made a whole machine unpushable for one module elsewhere.
|
|
|
|
|
//
|
|
|
|
|
// **A provider is granted exactly the consumers whose own resolution binds them to it** (novox/hq
|
|
|
|
|
// issue 274). The pair credentials on record say only whom this node was ever asked by: after a
|
|
|
|
|
// consumer of a provision that keeps its data was moved and pinned back (issue 273, ADR 0232), the
|
|
|
|
|
// credential from the provider it left was still on record, so that provider went on being asked for
|
|
|
|
|
// five databases nobody used and never retired them. A credential whose consumer is bound elsewhere
|
|
|
|
|
// is withdrawn here like one nobody asks for — the provider retires it and keeps its data (ADR 0230)
|
|
|
|
|
// — and returned beside the grants, for plan and push to say. It stays on record: it is the key to
|
|
|
|
|
// the login the provider keeps until `cleanup delete`, and to that data should a person pin it back.
|
|
|
|
|
func grantsFor(ctx context.Context, open *stores, node string) (
|
|
|
|
|
[]catalogue.Grant, []catalogue.Overflow, []catalogue.Unbound, error) {
|
|
|
|
|
inv := open.inventory
|
|
|
|
|
issued, err := inv.SecretsFrom(ctx, node)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Where each consumer is, so a provider that must reach back to one does not have to know how
|
|
|
|
|
// the mesh names machines.
|
|
|
|
|
shelf, err := inv.Catalogue(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
onNetwork, err := whereEveryoneIs(ctx, inv, shelf)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// What each consumer actually asked for, taken from that machine's own resolution rather than
|
|
|
|
|
// from a record beside it. A provider told to create a password and not what to create it for
|
|
|
|
|
// can do nothing with it, and the name a consumer wants is the consumer's to say.
|
|
|
|
|
out := make([]catalogue.Grant, 0, len(issued))
|
|
|
|
|
var withheld []catalogue.Overflow
|
|
|
|
|
var unbound []catalogue.Unbound
|
|
|
|
|
for _, s := range issued {
|
|
|
|
|
plan, settings, err := planFor(ctx, open, s.Consumer)
|
|
|
|
|
switch {
|
|
|
|
|
case unresolvable(err):
|
|
|
|
|
// Their set does not resolve. Skipped rather than fatal: this node is not the place
|
|
|
|
|
// to report another machine's problem, and a grant for something that is not going to
|
|
|
|
|
// run would have the provider create a user nothing uses.
|
|
|
|
|
continue
|
|
|
|
|
case err != nil:
|
|
|
|
|
// The mesh could not be asked what they wanted, which is not the same as their wanting
|
|
|
|
|
// nothing — and withholding a grant on that reading takes a consumer's access away
|
|
|
|
|
// (novox/hq 04-ISSUES/152).
|
|
|
|
|
return nil, nil, nil, fmt.Errorf("what %s asked of %s cannot be read: %w", s.Consumer, s.Name, err)
|
|
|
|
|
}
|
|
|
|
|
values, asks, err := plan.ContributionsFrom(s.Name, s.ConsumerModule, settings)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
// A port in there is the consumer's software port until this. The consumer is on another
|
|
|
|
|
// machine, so the assignment that moved it is that machine's — fetched here rather than
|
|
|
|
|
// looked for in this one's, which is the whole reason the co-located fix could not reach
|
|
|
|
|
// this case (novox/hq 04-ISSUES/038, the cross-node half).
|
|
|
|
|
published, err := portsOn(ctx, inv, s.Consumer, s.ConsumerModule)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
values = catalogue.AtPublishedPort(values, s.ConsumerModule, published)
|
|
|
|
|
from := s.ConsumerModule
|
|
|
|
|
if !asks {
|
|
|
|
|
// That module no longer wants this. Left empty, which is what the declaration reads
|
|
|
|
|
// as "nobody asks for it any more" — and is how a login is withdrawn rather than kept
|
|
|
|
|
// working for ever after its consumer went away.
|
|
|
|
|
from = ""
|
|
|
|
|
}
|
|
|
|
|
if bound := plan.BindsFrom(s.Name, s.ConsumerModule, s.Local); from != "" && !slices.Contains(bound, node) {
|
|
|
|
|
// It still asks, and not of this node: its resolution — the same one read above, so an
|
|
|
|
|
// unreadable one is the error above and never an empty answer here (issue 152) — binds
|
|
|
|
|
// this credential to another provider, or under this local name to none. Withdrawn
|
|
|
|
|
// exactly as a credential nobody asks for, and said.
|
|
|
|
|
from = ""
|
|
|
|
|
unbound = append(unbound, catalogue.Unbound{Provision: s.Name, Provider: node,
|
|
|
|
|
Consumer: s.Consumer, Module: s.ConsumerModule, Local: s.Local, BoundTo: bound})
|
|
|
|
|
}
|
|
|
|
|
// The consumer's identity slug, from its own manifest, carried on the grant so the provider
|
|
|
|
|
// derives the same login the consumer does (novox/hq ADR 0049). Judged against the bound of
|
|
|
|
|
// this provision, as the consumer's resolution states it from the provider's offer (ADR
|
|
|
|
|
// 0225): a consumer it would not fit is left out of the grants and said, rather than a login a
|
|
|
|
|
// provider silently shortened — and rather than this whole machine refused for it.
|
|
|
|
|
slug := ""
|
|
|
|
|
for _, mm := range plan.Modules {
|
|
|
|
|
if mm.Module == s.ConsumerModule {
|
|
|
|
|
slug = mm.Slug
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if from != "" {
|
|
|
|
|
bound := boundOfGrant(plan, s, node)
|
|
|
|
|
source := catalogue.IdentitySource(slug, s.ConsumerModule)
|
|
|
|
|
if catalogue.CheckIdentityWithin(s.Consumer, source, bound) != nil {
|
|
|
|
|
withheld = append(withheld, catalogue.Overflow{Provision: s.Name, Provider: node,
|
|
|
|
|
Consumer: s.Consumer, Module: s.ConsumerModule,
|
|
|
|
|
Identity: catalogue.ConsumerIdentity(s.Consumer, source), Bound: bound})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out = append(out, catalogue.Grant{
|
|
|
|
|
Provision: s.Name, Consumer: s.Consumer, At: onNetwork[s.Consumer],
|
|
|
|
|
From: from, Values: values, Slug: slug, Sealed: s.ForProvider, Local: s.Local})
|
|
|
|
|
}
|
|
|
|
|
return out, withheld, unbound, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// boundOfGrant is the identity bound the consumer's own resolution states for the requirement this
|
|
|
|
|
// grant answers. A requirement not found there is held to the tightest bound the mesh knows rather
|
|
|
|
|
// than to none: what the provider keeps of it is not known here.
|
|
|
|
|
func boundOfGrant(consumer catalogue.Resolution, s inventory.Secret, provider string) catalogue.IdentityBound {
|
|
|
|
|
for _, n := range consumer.Needs {
|
|
|
|
|
if n.Name == s.Name && n.For == s.ConsumerModule && n.From == provider {
|
|
|
|
|
return n.Identity
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return catalogue.DefaultIdentityBound
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// listensLines is what a person is told about what this module would open, and why — the same
|
|
|
|
|
// `why` every listens entry already carries for the firewall it also feeds (novox/hq ADR 0007), so
|
|
|
|
|
// deciding whether to assign a module can see what it would open before it opens it, not only
|
|
|
|
@@ -596,91 +468,6 @@ func shownAs(r map[string]any) string {
|
|
|
|
|
return heading
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// licencesFor is what this node can be answered with by record, and what it was put on.
|
|
|
|
|
//
|
|
|
|
|
// A mesh with no licences at all is the ordinary case and must not be an error: every existing
|
|
|
|
|
// mesh is one, and a control plane that refused to plan because nobody had bought an API key
|
|
|
|
|
// would be unusable for the thing it already does.
|
|
|
|
|
func licencesFor(ctx context.Context, open *stores, node string) (
|
|
|
|
|
map[string][]catalogue.Record, map[string]map[string]catalogue.Record, error) {
|
|
|
|
|
|
|
|
|
|
held, err := open.Licences(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
all, err := held.All(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
if len(all) == 0 {
|
|
|
|
|
return nil, nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offered := map[string][]catalogue.Record{}
|
|
|
|
|
byName := map[string]catalogue.Record{}
|
|
|
|
|
for _, one := range all {
|
|
|
|
|
record := catalogue.Record{Name: one.Name, Serves: one.Serves}
|
|
|
|
|
offered[licences.Provision] = append(offered[licences.Provision], record)
|
|
|
|
|
byName[one.Name] = record
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using := map[string]map[string]catalogue.Record{}
|
|
|
|
|
for _, one := range all {
|
|
|
|
|
holders, err := held.HoldersOf(ctx, one.Name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
for _, h := range holders {
|
|
|
|
|
if h.Node != node {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if using[h.Module] == nil {
|
|
|
|
|
using[h.Module] = map[string]catalogue.Record{}
|
|
|
|
|
}
|
|
|
|
|
using[h.Module][licences.Provision] = byName[one.Name]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return offered, using, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// keyFor is the licence key sealed to one machine, for one module.
|
|
|
|
|
//
|
|
|
|
|
// **Empty is not an error here.** The mesh discarded the plaintext when it was supplied, so a
|
|
|
|
|
// holder recorded afterwards genuinely has no key — and the declaration refuses that by name,
|
|
|
|
|
// where the module and the path are both in view, rather than here.
|
|
|
|
|
func keyFor(ctx context.Context, open *stores, licence, node, module string) (string, error) {
|
|
|
|
|
held, err := open.Licences(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return held.KeyFor(ctx, licence, node, module)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// managerPublicKeyFor is the manager node's public sealing key, but only when (node, module) is the
|
|
|
|
|
// licence's manager holder — empty otherwise.
|
|
|
|
|
//
|
|
|
|
|
// It is delivered to the manager module in its bound facts so it can re-seal a rotated refresh token
|
|
|
|
|
// to this node (novox/hq ADR 0050). Public, so it travels in the clear like any other bound fact; and
|
|
|
|
|
// scoped to the manager holder alone, so a consumer never receives it and nothing invites a consumer
|
|
|
|
|
// to seal anything.
|
|
|
|
|
func managerPublicKeyFor(
|
|
|
|
|
ctx context.Context, open *stores, inv *inventory.Inventory, licence, node, module string,
|
|
|
|
|
) (string, error) {
|
|
|
|
|
held, err := open.Licences(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
managerNode, managerModule, err := held.ManagerOf(ctx, licence)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if managerNode == "" || node != managerNode || module != managerModule {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
return inv.SealingKeyOf(ctx, node)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// servedOnNode is what a module on a node tells a consumer of one of its provisions, with THAT
|
|
|
|
|
// node's ports on it: the port the node was given (novox/hq ADR 0100) over the one the mesh
|
|
|
|
|
// assigned over the manifest's own, settled with the node's settings layers.
|
|
|
|
@@ -725,76 +512,6 @@ func portsGivenOn(ctx context.Context, inv *inventory.Inventory, node string,
|
|
|
|
|
return ports, layers, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// seatsOn is where a node put the holder of each mesh-scoped seat, by seat and by the port the
|
|
|
|
|
// holder's software uses — what ${seat:…} answers with (novox/hq 04-ISSUES/102).
|
|
|
|
|
//
|
|
|
|
|
// Read for every module in the catalogue that claims a seat, in this node's set or not: the store
|
|
|
|
|
// and the broker are given their ports at genesis, as settings on a module that may be registered
|
|
|
|
|
// and not yet assigned (04-ISSUES/085), and the control plane must follow that setting from the
|
|
|
|
|
// first declaration it composes for itself. A holder in this node's set wins over one that is not.
|
|
|
|
|
func seatsOn(ctx context.Context, inv *inventory.Inventory, shelf map[string]catalogue.Manifest,
|
|
|
|
|
node string, inSet []catalogue.Manifest, adopted bool, taken map[string]bool) (map[string]map[int]int, error) {
|
|
|
|
|
assigned := map[string]bool{}
|
|
|
|
|
for _, m := range inSet {
|
|
|
|
|
assigned[m.Module] = true
|
|
|
|
|
}
|
|
|
|
|
names := make([]string, 0, len(shelf))
|
|
|
|
|
for name := range shelf {
|
|
|
|
|
names = append(names, name)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(names)
|
|
|
|
|
seats := map[string]map[int]int{}
|
|
|
|
|
for _, name := range names {
|
|
|
|
|
m := shelf[name]
|
|
|
|
|
var claims []string
|
|
|
|
|
for _, c := range m.Claims {
|
|
|
|
|
if c.At() == catalogue.ScopeMesh {
|
|
|
|
|
claims = append(claims, c.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(claims) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// **Only a port the node was given or the mesh assigned — never the manifest's own
|
|
|
|
|
// number.** The sealed value the answer sits beside carries the port genesis wrote, which
|
|
|
|
|
// on a given-port node is the predecessor's; a manifest's long-form mapping is the
|
|
|
|
|
// catalogue's default, and answering with it would override the right number with one
|
|
|
|
|
// the mesh never checked (the contract in seat_into.go). And on an adopted node a holder
|
|
|
|
|
// assigned but not yet taken is the found container, on the ports it was found with, not
|
|
|
|
|
// the declaration's — so its mesh-assigned ports do not count there either; a given port
|
|
|
|
|
// does, because a given port is the found one by construction (ADR 0100).
|
|
|
|
|
ports, _, err := portsGivenOn(ctx, inv, node, m)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if adopted && !taken[name] {
|
|
|
|
|
layers, err := inv.SettingsFor(ctx, node, m.Module)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
ports = map[int]int{}
|
|
|
|
|
if given, err := catalogue.GivenPorts(m, layers); err == nil {
|
|
|
|
|
ports = given
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(ports) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, seat := range claims {
|
|
|
|
|
if seats[seat] == nil {
|
|
|
|
|
seats[seat] = map[int]int{}
|
|
|
|
|
}
|
|
|
|
|
for wanted, at := range ports {
|
|
|
|
|
if _, said := seats[seat][wanted]; said && !assigned[name] {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
seats[seat][wanted] = at
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return seats, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// portsOn is one module's assignments on one machine, by the port the software uses.
|
|
|
|
|
func portsOn(
|
|
|
|
|
ctx context.Context, inv *inventory.Inventory, node, module string,
|
|
|
|
@@ -812,45 +529,6 @@ func portsOn(
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// composeBusUsers is the bus's user list, for a push to the machine that runs the bus.
|
|
|
|
|
//
|
|
|
|
|
// Empty for every other machine, and for every machine while the mesh is on the bus it runs on
|
|
|
|
|
// today — where accounts are a management call and there is no file to write.
|
|
|
|
|
//
|
|
|
|
|
// **Composed on each push, never kept.** The list is a function of the mesh's records (who exists,
|
|
|
|
|
// what runs where, what each declares), and a stored copy would be a second account of who may reach
|
|
|
|
|
// the bus, able to disagree with the records while both looked internally consistent (ADR 0043).
|
|
|
|
|
//
|
|
|
|
|
// A user the mesh has never minted a password for is **left out and said**, not written as a user
|
|
|
|
|
// without one — the composer refuses that, because a user with no password is a user anybody is. That
|
|
|
|
|
// is an ordinary situation with an obvious remedy (`module issue`, or enrolling), so the push carries
|
|
|
|
|
// the rest rather than failing: a bus that is missing one module's user is a mesh where that module
|
|
|
|
|
// cannot connect, and a bus with no file at all is a mesh where nothing can.
|
|
|
|
|
func composeBusUsers(ctx context.Context, inv *inventory.Inventory,
|
|
|
|
|
onThisNode []catalogue.Manifest) (string, error) {
|
|
|
|
|
|
|
|
|
|
// **Not gated on which bus the controller is on, and that was a bug.** It read "compose this only
|
|
|
|
|
// once the mesh is on the new bus" — which cannot work, because the server needs its user list
|
|
|
|
|
// *before* anything moves onto it. Step 2 of the change is exactly that: the server stands in the
|
|
|
|
|
// mesh carrying nothing, on its own ports, while every node is still on the old bus (novox/hq
|
|
|
|
|
// ADR 0116). Under the old gating that step could not happen: the module would come up, find no
|
|
|
|
|
// accounts file, and its entrypoint would wait for one the controller had decided not to write.
|
|
|
|
|
//
|
|
|
|
|
// So the question is only whether this machine runs the module that asked for the file. A mesh
|
|
|
|
|
// that never moves has written a user list nothing reads, which costs a few hundred bytes on one
|
|
|
|
|
// node; the reverse cost a step that cannot be taken.
|
|
|
|
|
//
|
|
|
|
|
// Asked of what this push resolves to rather than of the seat's holder mesh-wide: the file is a
|
|
|
|
|
// resource of that module, so the question is whether it is here.
|
|
|
|
|
list, missing, err := busUserList(ctx, inv, onThisNode)
|
|
|
|
|
if len(missing) > 0 {
|
|
|
|
|
fmt.Printf("the bus's user list leaves out %d user(s) the mesh has minted no credential "+
|
|
|
|
|
"for: %s. Each is a user that cannot connect until one is issued\n",
|
|
|
|
|
len(missing), strings.Join(missing, ", "))
|
|
|
|
|
}
|
|
|
|
|
return list, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// busUserList is composeBusUsers without saying anything: the list, and the users left out of it
|
|
|
|
|
// for want of a credential. Asked on every send to decide whether the machine holding the bus must
|
|
|
|
|
// go first (novox/hq issue 249), where saying the same missing users each time would bury them.
|
|
|
|
@@ -905,60 +583,6 @@ func issuedOutsideBy(shelf map[string]catalogue.Manifest, n catalogue.Needed) (s
|
|
|
|
|
return catalogue.OffersIssuedOutside(shelf, n.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// issuedToHolder is whether the vault keeps a need's credential for its holder alone (issues 749, 757 and 779): its
|
|
|
|
|
// providing module is marked moved, so the vault holds the credential in a row of its own for each holder
|
|
|
|
|
// (filledTheNewWay), and its offer says either that the provider's software issues the provision's credential, or
|
|
|
|
|
// that the provider gives each consumer a login of its own (form pair, ADR 0158 §11). A credential every holder
|
|
|
|
|
// shares, or one a person gives, is neither.
|
|
|
|
|
func issuedToHolder(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
|
|
|
|
|
if n.ByRecord || n.Module == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
provider, known := shelf[n.Module]
|
|
|
|
|
if !known || !movedProvider(provider) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if _, issued := provider.IssuedCredentialOf(n.Name); issued {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return provider.CredentialFormOf(n.Name) == catalogue.CredentialFormPair
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// providerMoved is whether a need is answered by a module marked moved (issue 788), or by one whose offer of the
|
|
|
|
|
// provision says the provider helper answers it (issue 792).
|
|
|
|
|
func providerMoved(shelf map[string]catalogue.Manifest, n catalogue.Needed) bool {
|
|
|
|
|
if n.ByRecord || n.Module == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
provider, known := shelf[n.Module]
|
|
|
|
|
return known && (movedProvider(provider) || provider.AnsweredByHelperFor(n.Name))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// movedProvider is whether a provider's credentials are the vault's: the module is marked moved, or it holds the
|
|
|
|
|
// mesh-vault seat, whose pair rows have been in the vault since V3 (issue 793), so its consumers' credentials
|
|
|
|
|
// (gitea's internal token and admin password) are filled from those very rows with the values unchanged.
|
|
|
|
|
func movedProvider(m catalogue.Manifest) bool {
|
|
|
|
|
return m.Moved || slices.ContainsFunc(m.Claims, func(c catalogue.Claim) bool { return c.Name == "mesh-vault" })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func providerModuleOf(resolved catalogue.Resolution, open *stores, ctx context.Context, n catalogue.Needed) string {
|
|
|
|
|
for _, m := range resolved.Modules {
|
|
|
|
|
if _, shared := m.SharedCredentialOf(n.Name); shared {
|
|
|
|
|
return m.Module
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
for name, m := range shelf {
|
|
|
|
|
if _, shared := m.SharedCredentialOf(n.Name); shared {
|
|
|
|
|
return name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unheldLogger remembers what was last logged about each node's unmet seat dependencies, so the
|
|
|
|
|
// log says each change once (novox/hq ADR 0207), on stderr so `status --json` stays a document.
|
|
|
|
|
// A test gives its own writer and memory, and no longer shares the process's (novox/hq issue 486).
|
|
|
|
@@ -1074,27 +698,3 @@ func sortedNames[V any](m map[string]V) []string {
|
|
|
|
|
sort.Strings(out)
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wireBuilds is the build of each carried module that goes on the wire (novox/hq issue 1145): the build its assignment
|
|
|
|
|
// names when that assignment is of the commit carried, else the build made from that commit; a module with neither
|
|
|
|
|
// is not named.
|
|
|
|
|
func wireBuilds(carried map[string]string, runs map[string]chain.Ref, lookup func(module, commit string) (string, error)) (map[string]string, error) {
|
|
|
|
|
out := map[string]string{}
|
|
|
|
|
for module, commit := range carried {
|
|
|
|
|
if commit == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if ref, ok := runs[module]; ok && ref.Commit == commit && ref.Build != "" {
|
|
|
|
|
out[module] = ref.Build
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
build, err := lookup(module, commit)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if build != "" {
|
|
|
|
|
out[module] = build
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|