The node-engine reads a resource's module and a declaration's builds, says it does in every report (reads_modules), groups the apply's outcome per module with every failing resource named, and writes have.<node>.<module> and have.<node>.node to the bucket only when a value differs, never ahead of or in place of the report (novox/hq ADR 0429 section 4).
100 lines
3.4 KiB
Go
100 lines
3.4 KiB
Go
package apply
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
// failuresOf is every failure of an apply as the error carries it: the first among them, then the rest, so a module
|
|
// whose resource failed second is still named (issue 1132, and issue 500, which counted the rest and named none).
|
|
func failuresOf(failures []*Error) []Failure {
|
|
out := make([]Failure, 0, len(failures))
|
|
for _, f := range failures {
|
|
out = append(out, Failure{Resource: f.Resource, Err: f.Err})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ModuleResults groups an apply's outcome per module the declaration names (novox/hq ADR 0429 §4, issue 1132): the
|
|
// module's resources all applied, or it failed and Detail says which resource failed with its error, which resources
|
|
// were skipped behind a step that did not complete, or that its resources were not attempted because a step of no
|
|
// module stopped the apply. A module is named by what the declaration says of each resource (ModuleOfID), so one
|
|
// whose id would guess another is still its own.
|
|
//
|
|
// A failure that is no resource's (an error that is not an Error, or a resource of no module) belongs to no module and
|
|
// changes none: the node says it in its report. The report is the outcome of an apply that did not fail; an apply that
|
|
// failed carries its own account in the Error, and that is read in its place.
|
|
func ModuleResults(d *declaration.Declaration, report Report, err error) map[string]ModuleResult {
|
|
if d == nil {
|
|
return nil
|
|
}
|
|
out := map[string]ModuleResult{}
|
|
attempted := map[string]bool{}
|
|
var failed *Error
|
|
errors.As(err, &failed)
|
|
outcomes := report.Outcomes
|
|
if failed != nil {
|
|
outcomes = append(append([]Outcome(nil), outcomes...), failed.Done.Outcomes...)
|
|
}
|
|
skipped := map[string]string{}
|
|
for _, o := range outcomes {
|
|
module, ok := d.ModuleOfID(o.ID)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if o.Action == "skipped" {
|
|
skipped[module] = o.Detail
|
|
continue
|
|
}
|
|
attempted[o.ID] = true
|
|
}
|
|
var reasons = map[string][]string{}
|
|
for _, r := range d.Resources {
|
|
if module, ok := d.ModuleOfID(r.Identity()); ok {
|
|
out[module] = ModuleResult{}
|
|
if failed != nil && failed.Stopped && !attempted[r.Identity()] {
|
|
reasons[module] = append(reasons[module], fmt.Sprintf("%s was not attempted: %s stopped the apply", r.Identity(), failed.Resource))
|
|
}
|
|
}
|
|
}
|
|
if failed != nil {
|
|
for _, f := range failed.Failures {
|
|
if module, ok := d.ModuleOfID(f.Resource); ok {
|
|
if _, named := out[module]; named {
|
|
reasons[module] = append(reasons[module], fmt.Sprintf("%s failed: %v", f.Resource, f.Err))
|
|
}
|
|
}
|
|
}
|
|
if len(failed.Failures) == 0 {
|
|
if module, ok := d.ModuleOfID(failed.Resource); ok {
|
|
if _, named := out[module]; named {
|
|
reasons[module] = append(reasons[module], fmt.Sprintf("%s failed: %v", failed.Resource, failed.Err))
|
|
}
|
|
}
|
|
}
|
|
for _, module := range failed.Modules {
|
|
if _, named := out[module]; named {
|
|
why := skipped[module]
|
|
if why == "" {
|
|
why = "a step it declares did not complete"
|
|
}
|
|
reasons[module] = append(reasons[module], "gated: "+why)
|
|
}
|
|
}
|
|
}
|
|
for module, why := range skipped {
|
|
if _, named := out[module]; named && len(reasons[module]) == 0 {
|
|
reasons[module] = append(reasons[module], "gated: "+why)
|
|
}
|
|
}
|
|
for module, why := range reasons {
|
|
sort.Strings(why)
|
|
out[module] = ModuleResult{Failed: true, Detail: strings.Join(why, "; ")}
|
|
}
|
|
return out
|
|
}
|