Each act that changed what a node runs now writes that node's compositions, and its wants follow by the assigner's rules; a bus upgrade composes the bus's machines and answers the want the operator writes, since the assigner never moves the bus (ADR 0539 clauses 5, 7 and 15). A node-engine may ask its own backup holder before a data module moves.
150 lines
5.6 KiB
Go
150 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A provision whose credential only a person outside the mesh can give (novox/hq ADR 0337, issue 164, issue 165):
|
|
// given once for its provider, at the controller's terminal, and sealed in one act to every consumer that binds the
|
|
// provision from that provider now. The value is never read back, printed or kept.
|
|
|
|
// outsideOffer is the provider module's own name when `secret accept <node> <module> <name>` names a provision that
|
|
// module offers with a credential issued outside the mesh, and "" when it names anything else (an own secret, or a
|
|
// pair with --provider).
|
|
func outsideOffer(ctx context.Context, module, name, provider string) (string, error) {
|
|
if provider != "" {
|
|
return "", nil
|
|
}
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if m, known := shelf[module]; known && m.IssuedOutsideOf(name) {
|
|
return module, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// offeredOutsideBy is a module that offers the provision with a credential issued outside the mesh, or "".
|
|
func offeredOutsideBy(ctx context.Context, provision string) (string, error) {
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer open.Close()
|
|
shelf, err := open.inventory.Catalogue(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
module, _ := catalogue.OffersIssuedOutside(shelf, provision)
|
|
return module, nil
|
|
}
|
|
|
|
// consumersFrom is every consumer that binds a provision from one provider now, found by resolving every machine as
|
|
// its next send would, and the machines that could not be resolved, with why.
|
|
func consumersFrom(ctx context.Context, open *stores, provider, providerModule, provision string) (
|
|
[]inventory.PairEnd, []string, error) {
|
|
// One reading of the mesh for every machine this looks at.
|
|
ctx = withInputCache(ctx)
|
|
nodes, err := open.inventory.Nodes(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
var out []inventory.PairEnd
|
|
var unresolved []string
|
|
for _, n := range nodes {
|
|
plan, _, err := resolveNode(ctx, open, n.Name)
|
|
if err != nil {
|
|
unresolved = append(unresolved, fmt.Sprintf("%s: %s", n.Name, oneLine(err.Error())))
|
|
continue
|
|
}
|
|
out = append(out, bindsFrom(plan, n.Name, provider, providerModule, provision)...)
|
|
}
|
|
return out, unresolved, nil
|
|
}
|
|
|
|
// bindsFrom is every consumer on one machine whose resolution answers the provision from that provider. Pure.
|
|
func bindsFrom(plan catalogue.Resolution, machine, provider, providerModule, provision string) []inventory.PairEnd {
|
|
var out []inventory.PairEnd
|
|
for _, need := range plan.Needs {
|
|
if need.ByRecord || need.Name != provision || need.From != provider {
|
|
continue
|
|
}
|
|
if need.Module != "" && need.Module != providerModule {
|
|
continue
|
|
}
|
|
out = append(out, inventory.PairEnd{Consumer: machine, ConsumerModule: need.For, Local: need.Local})
|
|
}
|
|
sort.Slice(out, func(i, j int) bool {
|
|
return out[i].ConsumerModule+"\x00"+out[i].Local < out[j].ConsumerModule+"\x00"+out[j].Local
|
|
})
|
|
return out
|
|
}
|
|
|
|
// acceptForProvision gives a provision's credential issued outside the mesh, once for its provider (ADR 0337 §3).
|
|
func acceptForProvision(ctx context.Context, provider, providerModule, provision, value string) error {
|
|
open, err := openStores(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer open.Close()
|
|
consumers, unresolved, err := consumersFrom(ctx, open, provider, providerModule, provision)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
accepted, err := open.inventory.AcceptSecretForProvision(ctx, provision, provider, providerModule, value, consumers)
|
|
value = ""
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Print(acceptedSaid(provider, providerModule, provision, accepted, unresolved))
|
|
if len(accepted.Sealed) == 0 {
|
|
return nil
|
|
}
|
|
// Composed into each consumer's composition (novox/hq ADR 0539 clause 5): a new revision its want then names.
|
|
if _, err := composeModules(ctx, open, accepted.Machines); err != nil {
|
|
return fmt.Errorf("%s is sealed and its compositions on %s could not be written: %w", provision,
|
|
strings.Join(accepted.Machines, ", "), err)
|
|
}
|
|
fmt.Printf(" composed for %s; each consumer starts again on it once its want names the new composition\n",
|
|
strings.Join(accepted.Machines, ", "))
|
|
return nil
|
|
}
|
|
|
|
// acceptedSaid is what one accept for a provider did, in words that never carry the value. Pure.
|
|
func acceptedSaid(provider, providerModule, provision string, a inventory.ProvisionAccepted, unresolved []string) string {
|
|
var b strings.Builder
|
|
if len(a.Sealed) == 0 {
|
|
fmt.Fprintf(&b, "%s from %s on %s: no consumer binds it now, so nothing was sealed; a consumer that binds "+
|
|
"later waits for it, and this line given again seals it to every consumer then\n", provision,
|
|
providerModule, provider)
|
|
} else {
|
|
var names []string
|
|
for _, c := range a.Sealed {
|
|
names = append(names, fmt.Sprintf("%s on %s%s", c.ConsumerModule, c.Consumer, asLocal(c.Local)))
|
|
}
|
|
fmt.Fprintf(&b, "%s from %s on %s is given, sealed to %s and to %s\n", provision, providerModule, provider,
|
|
strings.Join(names, ", "), provider)
|
|
fmt.Fprintf(&b, " the mesh cannot read it back, never makes one of its own for it, and will not rotate it\n")
|
|
}
|
|
for _, c := range a.Unsealable {
|
|
fmt.Fprintf(&b, " not sealed to %s on %s: no sealing key on that machine; give it again once it has one\n",
|
|
c.ConsumerModule, c.Consumer)
|
|
}
|
|
for _, u := range unresolved {
|
|
fmt.Fprintf(&b, " not known whether a consumer on %s binds it, which did not resolve; give it again once it does\n", u)
|
|
}
|
|
return b.String()
|
|
}
|