The first pass of the adapter resolved every machine with no settings in the Input, so a provider whose served fact says ${setting:domain} could not be settled and the whole Input failed (the equality try on the live facts). The settings of every module that has any are read first; a module's settings that do not read are an error only for a machine that resolves to it. A recorded module that cannot be kept at the build a machine runs refuses that machine alone, as planFor did, and no longer the whole Input. The bus's refusal (no user has a credential) is carried for the machine that runs the bus.
286 lines
9.2 KiB
Go
286 lines
9.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/compose"
|
|
"git.novox.be/novox/mesh-sdk/go/overlay"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// The second half of the adapter (issue 1383): the credential and build fields of the Input, read and never made.
|
|
|
|
// readCredentialsAndBuilds fills what the first half left: the credentials, the own secrets, the vault's boxes, the bus,
|
|
// each node's serving key and certificate, and the builds.
|
|
func readCredentialsAndBuilds(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved,
|
|
shape inputShape, busRefused *error) ([]inventory.PairCredential, error) {
|
|
if err := readIdentity(ctx, open, in); err != nil {
|
|
return nil, err
|
|
}
|
|
wouldMake, err := readCredentials(ctx, open, in, plans, busRefused)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return wouldMake, readBuilds(ctx, open, in, plans, shape)
|
|
}
|
|
|
|
// readIdentity is the mesh's authority and each node's serving key and the certificate kept for it, for a mesh in which a
|
|
// module asks for a certificate. It establishes and issues nothing: a certificate not kept yet is composed with a stand-in, and
|
|
// issued by the send (certificateFor).
|
|
func readIdentity(ctx context.Context, open *stores, in *compose.Input) error {
|
|
needs := false
|
|
for _, m := range in.Catalogue {
|
|
needs = needs || m.Certificate != nil
|
|
}
|
|
if !needs {
|
|
return nil
|
|
}
|
|
ident, err := open.Identity(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if certificate, known, err := ident.KnownAuthority(ctx); err != nil {
|
|
return err
|
|
} else if known {
|
|
in.Authority = certificate
|
|
}
|
|
nodes, err := open.inventory.Nodes(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ids := map[string]string{}
|
|
for _, n := range nodes {
|
|
ids[n.Name] = n.ID
|
|
}
|
|
for i := range in.Nodes {
|
|
n := &in.Nodes[i]
|
|
if n.ServingKey, err = ident.ServingKeyOf(ctx, ids[n.Name]); err != nil {
|
|
return err
|
|
}
|
|
if n.ServingKey == "" {
|
|
continue
|
|
}
|
|
if certificate, known, err := ident.KeptCertificate(ctx, overlay.InternalName(n.Name), n.ServingKey); err != nil {
|
|
return err
|
|
} else if known {
|
|
n.Certificate = certificate
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readCredentials is how each pair credential a node takes from elsewhere stands, the credentials made for each provider's
|
|
// consumers, the own secrets, the vault's boxes and the bus. It reads and never makes: a credential a send would make is
|
|
// left out (the composition's would-make), a credential only a person gives and nobody gave is not-given, and one the send
|
|
// would be refused is refused with the controller's words.
|
|
func readCredentials(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved,
|
|
busRefused *error) ([]inventory.PairCredential, error) {
|
|
var wouldMake []inventory.PairCredential
|
|
inv := open.inventory
|
|
shelf := in.Catalogue
|
|
nodeOf := map[string]compose.Node{}
|
|
for _, n := range in.Nodes {
|
|
nodeOf[n.Name] = n
|
|
}
|
|
names := make([]string, 0, len(plans))
|
|
for name := range plans {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
resolved := plans[name]
|
|
nodeShelf := shelfWith(shelf, nodeOf[name].Kept)
|
|
for _, need := range resolved.Plan.Needs {
|
|
if need.ByRecord {
|
|
continue
|
|
}
|
|
p := inventory.PairCredential{Name: need.Name, Consumer: name, ConsumerModule: need.For,
|
|
Provider: need.From, Local: need.Local}
|
|
if module, outside := issuedOutsideBy(nodeShelf, need); outside {
|
|
p.Kind, p.ProviderModule = inventory.PairOutside, module
|
|
} else if need.SharedOwn != "" {
|
|
p.Kind, p.Own = inventory.PairShared, need.SharedOwn
|
|
p.ProviderModule = sharedProviderModule(resolved.Plan, shelf, need)
|
|
}
|
|
state, err := inv.PairCredentialState(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := compose.Credential{Provision: need.Name, Consumer: name, ConsumerModule: need.For,
|
|
Provider: need.From, Local: need.Local}
|
|
switch state.State {
|
|
case inventory.PairHeld:
|
|
c.State, c.Sealed = compose.CredentialHeld, state.Sealed
|
|
case inventory.PairNotGiven:
|
|
c.State, c.Why = compose.CredentialNotGiven, state.Why
|
|
case inventory.PairRefused:
|
|
c.State, c.Why = compose.CredentialRefused, state.Why
|
|
default:
|
|
// Left out: a need with no entry is the credential the next send makes, and perform makes it.
|
|
wouldMake = append(wouldMake, p)
|
|
continue
|
|
}
|
|
in.Credentials = append(in.Credentials, c)
|
|
}
|
|
}
|
|
|
|
// The credentials made for each provider's consumers, so the grants it must create are read from them.
|
|
for _, n := range in.Nodes {
|
|
rows, err := inv.SecretsFrom(ctx, n.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, s := range rows {
|
|
in.Issued = append(in.Issued, compose.Issued{Provision: s.Name, Consumer: s.Consumer,
|
|
ConsumerModule: s.ConsumerModule, Local: s.Local, Provider: n.Name, ForProvider: s.ForProvider})
|
|
}
|
|
}
|
|
own, err := inv.OwnSecretRows(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range own {
|
|
in.OwnSecrets = append(in.OwnSecrets, compose.OwnSecretRow{Node: r.Node, Module: r.Module, Name: r.Name,
|
|
Origin: r.Origin, SealedTo: r.SealedTo, Sealed: r.Sealed})
|
|
}
|
|
for _, n := range in.Nodes {
|
|
boxes, err := inv.KeptCoreBoxes(ctx, n.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for module, byName := range boxes {
|
|
for name, b := range byName {
|
|
in.KeptCoreBoxes = append(in.KeptCoreBoxes, compose.CoreBox{Node: n.Name, Module: module, Name: name,
|
|
Generation: b.Generation, SealedTo: b.SealedTo, Box: b.Box})
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(in.KeptCoreBoxes, func(i, j int) bool {
|
|
a, b := in.KeptCoreBoxes[i], in.KeptCoreBoxes[j]
|
|
if a.Node != b.Node {
|
|
return a.Node < b.Node
|
|
}
|
|
if a.Module != b.Module {
|
|
return a.Module < b.Module
|
|
}
|
|
return a.Name < b.Name
|
|
})
|
|
|
|
// The bus: its user list as the controller composes it, for the machine that runs it, and who the mesh has minted a
|
|
// credential for.
|
|
var bus []catalogue.Manifest
|
|
for _, m := range shelf {
|
|
if m.BusUsers != "" && m.ClaimsSeat("mesh-broker") {
|
|
bus = append(bus, m)
|
|
}
|
|
}
|
|
if len(bus) > 0 {
|
|
list, missing, err := busUserList(ctx, inv, bus)
|
|
if err != nil && !strings.Contains(err.Error(), "not one user has a credential") {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
// The machine that runs the bus is refused, as the controller's own composition refused it: a list that
|
|
// refuses every connection in the mesh is never composed (refusedBus); the other machines compose.
|
|
*busRefused = err
|
|
}
|
|
in.BusUsers, in.BusMissing = list, missing
|
|
}
|
|
if in.BusMinted, err = inv.BusMinted(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if in.BusMemberships, err = inv.BusMemberships(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return wouldMake, nil
|
|
}
|
|
|
|
// readBuilds is the build half of the Input: every repository the mesh built, each module's current build, and the build made
|
|
// from a commit of a module for the machines whose node-engine reads them.
|
|
func readBuilds(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved, shape inputShape) error {
|
|
inv := open.inventory
|
|
held, err := inv.Held(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for repository := range held {
|
|
in.Built = append(in.Built, repository)
|
|
}
|
|
sort.Strings(in.Built)
|
|
in.CurrentBuilds = make(map[string]string, len(shape.current))
|
|
for module, b := range shape.current {
|
|
in.CurrentBuilds[module] = b.Commit
|
|
}
|
|
// The build made from each commit a declaration may carry, for the machines that read a build per module.
|
|
for _, n := range in.Nodes {
|
|
if !n.ReadsModules {
|
|
continue
|
|
}
|
|
resolved, ok := plans[n.Name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, m := range resolved.Plan.Modules {
|
|
for _, commit := range []string{shape.current[m.Module].Commit, shape.kept[n.Name][m.Module], shape.sent[n.Name][m.Module]} {
|
|
if commit == "" {
|
|
continue
|
|
}
|
|
if _, done := in.BuildIDs[m.Module][commit]; done {
|
|
continue
|
|
}
|
|
build, err := inv.BuildOf(ctx, m.Module, commit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if in.BuildIDs == nil {
|
|
in.BuildIDs = map[string]map[string]string{}
|
|
}
|
|
if in.BuildIDs[m.Module] == nil {
|
|
in.BuildIDs[m.Module] = map[string]string{}
|
|
}
|
|
in.BuildIDs[m.Module][commit] = build
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// shelfWith is the shelf with each module a node keeps at the build it runs put in place of the current one: a copy.
|
|
func shelfWith(shelf map[string]catalogue.Manifest, kept map[string]catalogue.Manifest) map[string]catalogue.Manifest {
|
|
if len(kept) == 0 {
|
|
return shelf
|
|
}
|
|
out := make(map[string]catalogue.Manifest, len(shelf)+len(kept))
|
|
for name, m := range shelf {
|
|
out[name] = m
|
|
}
|
|
for name, m := range kept {
|
|
out[name] = m
|
|
}
|
|
return out
|
|
}
|
|
|
|
// sharedProviderModule is which module answers a shared credential on the providing node: the one in the plan, else the first
|
|
// the catalogue names (providerModuleOf, in an order that does not move between calls).
|
|
func sharedProviderModule(plan catalogue.Resolution, shelf map[string]catalogue.Manifest, n catalogue.Needed) string {
|
|
for _, m := range plan.Modules {
|
|
if _, shared := m.SharedCredentialOf(n.Name); shared {
|
|
return m.Module
|
|
}
|
|
}
|
|
names := make([]string, 0, len(shelf))
|
|
for name := range shelf {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
if _, shared := shelf[name].SharedCredentialOf(n.Name); shared {
|
|
return name
|
|
}
|
|
}
|
|
return ""
|
|
}
|