129 lines
5.2 KiB
Go
129 lines
5.2 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-contracts/go/seats/meshconfig"
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient"
|
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// A config value renamed (design 32 *A config value renamed*, DLV-C5): the checker asks the seat mesh-config's verb
|
|
// renames, never the module, for every change that brings `renamed-from` or drops one. A holder that does not serve the
|
|
// verb refuses `renamed-from`, naming that; while a rename's window is open, dropping its `renamed-from` is refused,
|
|
// naming the nodes holding it open; a check that cannot ask fails, saying so.
|
|
|
|
// RenamesSeat is the verb the checker asks, as its manifest's `invokes` names it.
|
|
var RenamesSeat = "seat:" + meshconfig.Seat + "." + meshconfig.VerbRenames
|
|
|
|
// ErrRenamesNotServed is the answer of Renames when the seat's holder serves no verb renames.
|
|
var ErrRenamesNotServed = errors.New("the holder of the seat " + meshconfig.Seat + " does not serve its verb " + meshconfig.VerbRenames)
|
|
|
|
// Renames asks the seat mesh-config's verb renames for a module's open renames.
|
|
type Renames interface {
|
|
Renames(ctx context.Context, module string) (meshconfig.RenamesAnswer, error)
|
|
}
|
|
|
|
// renameChange is what a pull request's head does to a module's `renamed-from` against the trunk: the declarations it
|
|
// brings (key from old name) and the ones it drops (key, old name).
|
|
type renameChange struct {
|
|
brought, dropped map[string]string
|
|
}
|
|
|
|
// renamesOf compares a module's config declarations at the trunk and at the head.
|
|
func renamesOf(was, now catalogue.Manifest) renameChange {
|
|
rc := renameChange{brought: map[string]string{}, dropped: map[string]string{}}
|
|
for key, d := range now.Settings {
|
|
if d.RenamedFrom != "" && was.Settings[key].RenamedFrom != d.RenamedFrom {
|
|
rc.brought[key] = d.RenamedFrom
|
|
}
|
|
}
|
|
for key, d := range was.Settings {
|
|
if d.RenamedFrom != "" && now.Settings[key].RenamedFrom != d.RenamedFrom {
|
|
rc.dropped[key] = d.RenamedFrom
|
|
}
|
|
}
|
|
return rc
|
|
}
|
|
|
|
// judgeRenames fails the check for a rename the seat refuses, a window a change would drop, or an ask that cannot be made.
|
|
func (st *state) judgeRenames(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue {
|
|
if st.c.Try != nil {
|
|
return nil
|
|
}
|
|
var out *meshdeliverychecker.VerdictsByGroupHeadsValue
|
|
failed := func(words string) {
|
|
f := FailedFirst{Machine: meshconfig.Seat, Words: words}
|
|
if out == nil {
|
|
out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &f}
|
|
return
|
|
}
|
|
addFailure(out, f)
|
|
}
|
|
for _, p := range st.c.Pulls {
|
|
for _, module := range p.Modules {
|
|
was, now, ok, err := st.manifestPair(ctx, p, module)
|
|
if err != nil {
|
|
failed(fmt.Sprintf("%s (%s): its config renames cannot be judged: %v", module, pullKey(p), err))
|
|
continue
|
|
}
|
|
if !ok {
|
|
was = catalogue.Manifest{} // a module new on the pull request brings every rename it declares
|
|
if now, ok = st.headManifest(ctx, p, module); !ok {
|
|
continue
|
|
}
|
|
}
|
|
rc := renamesOf(was, now)
|
|
if len(rc.brought) == 0 && len(rc.dropped) == 0 {
|
|
continue
|
|
}
|
|
if st.j.Renames == nil {
|
|
failed(fmt.Sprintf("%s (%s) changes a renamed-from, and this checker cannot ask %s", module, pullKey(p), RenamesSeat))
|
|
continue
|
|
}
|
|
answer, err := st.j.Renames.Renames(ctx, module)
|
|
switch {
|
|
case errors.Is(err, ErrRenamesNotServed):
|
|
for _, key := range slices.Sorted(maps.Keys(rc.brought)) {
|
|
failed(fmt.Sprintf("%s (%s) renames %s from %s: refused, %v, so the value cannot be moved", module, pullKey(p), key, rc.brought[key], err))
|
|
}
|
|
for _, key := range slices.Sorted(maps.Keys(rc.dropped)) {
|
|
failed(fmt.Sprintf("%s (%s) drops renamed-from %s of %s: refused, %v, so whether its window is open is not known", module, pullKey(p), rc.dropped[key], key, err))
|
|
}
|
|
continue
|
|
case err != nil:
|
|
failed(fmt.Sprintf("%s (%s) changes a renamed-from, and the check cannot ask %s: %v", module, pullKey(p), RenamesSeat, err))
|
|
continue
|
|
}
|
|
for _, key := range slices.Sorted(maps.Keys(rc.dropped)) {
|
|
for _, r := range answer.Renames {
|
|
if r.Module == module && r.From == rc.dropped[key] && r.To == key && len(r.Open) > 0 {
|
|
open := slices.Sorted(slices.Values(r.Open))
|
|
failed(fmt.Sprintf("%s (%s) drops renamed-from %s of %s while its rename window is open on %s", module, pullKey(p), r.From, key, strings.Join(open, ", ")))
|
|
}
|
|
}
|
|
}
|
|
st.c.note(fmt.Sprintf("%s.%s answered for %s (%s)", meshconfig.Seat, meshconfig.VerbRenames, module, pullKey(p)))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// headManifest is a module's manifest at the pull request's head, new on it: by its candidate's digest, else the forge's.
|
|
func (st *state) headManifest(ctx context.Context, p Pull, module string) (catalogue.Manifest, bool) {
|
|
if digest := p.Fps[module].Manifest; digest != "" && st.j.Read != nil {
|
|
if st.j.Manifests != nil && st.j.Manifests.ManifestDigests()[module] == digest {
|
|
return catalogue.Manifest{}, false // unchanged
|
|
}
|
|
m, err := st.j.Read.Manifest(ctx, digest)
|
|
return m, err == nil
|
|
}
|
|
return catalogue.Manifest{}, false
|
|
}
|