package updates import ( "context" "os" "os/exec" "strings" "time" ) // Ubuntu phases some -updates releases: a host is selected at random for each // update, and until it is, apt lists the package as upgradable while an // upgrade defers it. Reporting those as pending made a freshly patched host // look unpatched, so they are flagged and left out of pending counts. The // -security pocket is never phased. // aptPhasedDeferred asks apt which upgrades it would defer, by simulating an // upgrade. The simulation takes no lock and changes nothing. LC_ALL=C keeps // the heading parsePhasedDeferred looks for in English. Any failure reports no // phased packages: the update list is then exactly what it was before this // existed, never shorter. func aptPhasedDeferred() map[string]bool { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "apt-get", "-s", "-o", "Debug::NoLocking=1", "upgrade") cmd.Env = append(os.Environ(), "LC_ALL=C", "DEBIAN_FRONTEND=noninteractive") out, err := cmd.Output() if err != nil { return nil } return parsePhasedDeferred(string(out)) } // parsePhasedDeferred reads the package names apt lists under "The following // upgrades have been deferred due to phasing:". The names follow on indented // lines; the block ends at the first line that is not indented. func parsePhasedDeferred(out string) map[string]bool { phased := map[string]bool{} in := false for _, line := range strings.Split(out, "\n") { if strings.Contains(line, "deferred due to phasing") { in = true continue } if !in { continue } if line == "" || (line[0] != ' ' && line[0] != '\t') { in = false continue } for _, name := range strings.Fields(line) { phased[name] = true } } return phased } // markPhased flags the updates apt would defer. func markPhased(ups []PackageUpdate, phased map[string]bool) { for i := range ups { if phased[ups[i].Name] { ups[i].Phased = true } } } // CountInstallable is the number of updates an upgrade would actually install // now: phased updates are pending but not yet installable. func CountInstallable(ups []PackageUpdate) int { n := 0 for _, u := range ups { if !u.Phased { n++ } } return n }