Files
mesh-catalog/modules/pacman/cmd/pacman-tools/aftercheck.go
T

152 lines
4.6 KiB
Go

package main
// The after-upgrade check (novox/hq ADR 0508 §4), written to the run as its last step: the .pacnew and .pacsave
// files the transaction left, the distribution's news since the node's previous upgrade, the units failed now
// that were not failed in the record, and whether a restart is needed (the running kernel is no longer the one
// its package installed). It passes when the transaction succeeded and no unit newly failed (and, with the
// second part, every DKMS module is built); the rest is said and does not stop the round. The mesh restarts no
// machine (ADR 0477 §5).
import (
"sort"
"strings"
"time"
)
// AfterCheck is the after-upgrade check of a run whose transaction ended, against the record taken before it
// (nil when there is none: every unit failed now is then said as newly failed).
func (u *Upgrades) AfterCheck(run Run, rec *Record) Check {
c := Check{Pacnew: []string{}, Pacsave: []string{}, News: []NewsTitle{}, Mapped: []Mapped{}, NewlyFailed: []string{}, Unbuilt: []DKMSBuild{}}
succeeded := transactionSucceeded(run)
var started time.Time
for _, s := range run.Steps {
if s.Step == StepStarted {
started = s.At
}
}
if !started.IsZero() {
if b := u.transactionBlock(run.Kind, started); b != nil {
c.Pacnew = append(c.Pacnew, b.Pacnew...)
c.Pacsave = append(c.Pacsave, b.Pacsave...)
}
}
c.News, c.NewsError = u.newsSince(u.previousUpgrade(run))
healthy := true
if failed, err := u.Machine.FailedUnits(); err != nil {
healthy = false
c.NewlyFailed = append(c.NewlyFailed, "(the failed units could not be read: "+err.Error()+")")
} else {
before := map[string]bool{}
if rec != nil {
for _, f := range rec.FailedUnits {
before[f] = true
}
}
for _, f := range failed {
if !before[f] {
c.NewlyFailed = append(c.NewlyFailed, f)
}
}
}
c.RunningKernel, c.InstalledKernel, c.RestartNeeded = u.restartNeeded()
c.Passed = succeeded && healthy && len(c.NewlyFailed) == 0
u.afterCheckII(&c)
return c
}
// previousUpgrade is when the node's upgrade before this run started: its previous run with a transaction.
func (u *Upgrades) previousUpgrade(run Run) time.Time {
runs, err := u.ownRuns()
if err != nil {
return time.Time{}
}
var last time.Time
for _, r := range runs {
if r.Run.Run >= run.Run || r.Kind != RunUpgrade {
continue
}
for _, s := range r.Steps {
if s.Step == StepStarted && s.At.After(last) {
last = s.At
}
}
}
return last
}
// newsSince is the distribution's news published after a time, titles only; or why it could not be read.
func (u *Upgrades) newsSince(since time.Time) ([]NewsTitle, string) {
body, err := fetch(NewsFeed)
if err != nil {
return []NewsTitle{}, err.Error()
}
items, err := ParseNews(body, since)
if err != nil {
return []NewsTitle{}, "the feed could not be read: " + err.Error()
}
titles := []NewsTitle{}
for _, it := range items {
titles = append(titles, NewsTitle{Title: it.Title, Link: it.Link, Published: it.Published})
}
return titles, ""
}
// kernelTag is a release's flavour, its letters alone: `7.2.8-arch1-2` is arch, `6.18.54-2-lts` is lts.
func kernelTag(release string) string {
return strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' {
return r
}
return -1
}, release)
}
// installedKernels are the releases the installed kernel packages install, sorted.
func (m *Machine) installedKernels() ([]string, error) {
out, err := m.Out("pacman", "-Qq")
if err != nil {
return nil, err
}
var releases []string
for _, name := range lines(out) {
name = strings.TrimSpace(name)
if name != "linux" && !strings.HasPrefix(name, "linux-") {
continue
}
release, kernel, err := m.kernelRelease(name)
if err != nil {
return nil, err
}
if kernel {
releases = append(releases, release)
}
}
sort.Strings(releases)
return releases, nil
}
// restartNeeded compares the running kernel with what its package installs now: when no installed kernel is the
// running one, the one of its flavour is what a restart would boot.
func (u *Upgrades) restartNeeded() (running, installed string, needed bool) {
raw, err := u.Machine.ReadFile(OSRelease)
if err != nil {
return "(the running kernel could not be read: " + err.Error() + ")", "", false
}
running = strings.TrimSpace(string(raw))
kernels, err := u.Machine.installedKernels()
if err != nil {
return running, "(the installed kernels could not be read: " + err.Error() + ")", false
}
for _, k := range kernels {
if k == running {
return running, k, false
}
}
for _, k := range kernels {
if kernelTag(k) == kernelTag(running) {
return running, k, true
}
}
return running, "", true
}