un-ghetto errata check

This commit is contained in:
Aaron Bieber 2020-03-29 15:42:12 -06:00
parent 874d25b388
commit b20c2e6480
3 changed files with 142 additions and 100 deletions

203
errata.go
View File

@ -1,9 +1,11 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -12,118 +14,149 @@ import (
"golang.org/x/net/html" "golang.org/x/net/html"
) )
// Errata stores all of the erratas grouped by date // Erratum are our individual chunks of errata
type Errata struct { type Erratum struct {
ID int ID int
Type string
Date time.Time Date time.Time
Coverage string
Desc string Desc string
Patch string
Link string Link string
Sig []string
} }
// Errati is a collection of Errata // Fetch pulls down and parses the information for a given Erratum
type Errati struct { func (e *Erratum) Fetch() error {
List []Errata resp, err := http.Get(e.Link)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
// First two lines are our signature
// 3rd line is date
// everything from the date down to ^Index is our description
count := 0
var descr []string
var patch []string
re := regexp.MustCompile(`^Index:`)
matched := false
for scanner.Scan() {
s := scanner.Text()
if count < 2 {
e.Sig = append(e.Sig, s)
}
if count == 3 {
parts := strings.Split(s, ",")
if len(parts) == 3 {
d := fmt.Sprintf("%s,%s",
strings.Trim(parts[1], " "),
strings.Replace(parts[2], ":", "", -1))
e.Date, err = time.Parse("January 2, 2006", d)
if err != nil {
return err
}
}
}
if count > 3 {
if re.MatchString(s) {
matched = true
}
if !matched {
descr = append(descr, s)
} else {
patch = append(patch, s)
}
}
count = count + 1
}
if err := scanner.Err(); err != nil {
return err
}
e.Desc = strings.Join(descr, "\n")
e.Patch = strings.Join(patch, "\n")
return nil //fmt.Errorf("crap")
}
// Errata is a collection of Errata
type Errata struct {
List []Erratum
Length int Length int
} }
// By is the type of a "less" function that defines the ordering of its Planet arguments. // By is the type of a "less" function that defines the ordering of its Planet arguments.
type By func(p1, p2 *Errata) bool type By func(p1, p2 *Erratum) bool
// Sort is a method on the function type, By, that sorts the argument slice according to the function. // Sort is a method on the function type, By, that sorts the argument slice according to the function.
func (by By) Sort(erratas []Errata) { func (by By) Sort(errata []Erratum) {
es := &errataSorter{ es := &errataSorter{
erratas: erratas, errata: errata,
by: by, by: by,
} }
sort.Sort(es) sort.Sort(es)
} }
// errataSorter joins a By function and a slice of Errati to be sorted. // errataSorter joins a By function and a slice of Errata to be sorted.
type errataSorter struct { type errataSorter struct {
erratas []Errata errata []Erratum
by func(p1, p2 *Errata) bool // Closure used in the Less method. by func(p1, p2 *Erratum) bool // Closure used in the Less method.
} }
// Len is part of sort.Interface. // Len is part of sort.Interface.
func (s *errataSorter) Len() int { func (s *errataSorter) Len() int {
return len(s.erratas) return len(s.errata)
} }
// Swap is part of sort.Interface. // Swap is part of sort.Interface.
func (s *errataSorter) Swap(i, j int) { func (s *errataSorter) Swap(i, j int) {
s.erratas[i], s.erratas[j] = s.erratas[j], s.erratas[i] s.errata[i], s.errata[j] = s.errata[j], s.errata[i]
} }
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *errataSorter) Less(i, j int) bool { func (s *errataSorter) Less(i, j int) bool {
return s.by(&s.erratas[i], &s.erratas[j]) return s.by(&s.errata[i], &s.errata[j])
} }
// ParseErrata does the actual parsing of html (poorly!) // ParseErrata does the actual parsing of html
func ParseErrata(body io.Reader) (*Errati, error) { func ParseErrata(body io.Reader, baseURL string) (*Errata, error) {
var data []Errata var data []Erratum
var errati = &Errati{} var errata = &Errata{}
doc, err := html.Parse(body) doc, err := html.Parse(body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var f func(*html.Node) var f func(*html.Node)
f = func(node *html.Node) { f = func(node *html.Node) {
if node.Type == html.ElementNode && node.Data == "strong" { if node.Type == html.ElementNode && node.Data == "a" {
if node.FirstChild != nil { if node.FirstChild != nil {
var e Errata var e Erratum
var err error var err error
parts := strings.Split(node.FirstChild.Data, ": ")
for _, a := range node.Attr {
if a.Key == "href" {
parts := strings.Split(a.Val, "_")
if len(parts) >= 2 && a.Val != "" {
e.Link = fmt.Sprintf("%s%s", baseURL, a.Val)
e.ID, err = strconv.Atoi(parts[0]) e.ID, err = strconv.Atoi(parts[0])
if err != nil { if err != nil {
return // Not a link we care about
}
e.Type = parts[1]
e.Date, err = time.Parse("January 02, 2006", parts[2])
if err != nil {
return
}
// TODO: not this.
if node.NextSibling != nil &&
node.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.FirstChild.Data != "" {
e.Coverage = node.NextSibling.NextSibling.FirstChild.Data
}
if node.NextSibling != nil &&
node.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.Data != "" {
e.Desc = node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.Data
e.Desc = strings.TrimRight(e.Desc, "\n")
e.Desc = strings.TrimLeft(e.Desc, "\n")
}
if node.NextSibling != nil &&
node.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil &&
node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling != nil {
n := node.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling
for _, a := range n.Attr {
if a.Key == "href" {
e.Link = a.Val
break break
} }
}
}
data = append(data, e) data = append(data, e)
errati.Length = errati.Length + 1 }
}
}
return return
} }
} }
@ -133,18 +166,18 @@ func ParseErrata(body io.Reader) (*Errati, error) {
} }
f(doc) f(doc)
byDate := func(p1, p2 *Errata) bool { byID := func(p1, p2 *Erratum) bool {
return p1.Date.Unix() < p2.Date.Unix() return p1.ID < p2.ID
} }
By(byDate).Sort(data) By(byID).Sort(data)
errati.List = data errata.List = data
return errati, nil return errata, nil
} }
// ParseRemoteErrata grabs all of the OpenBSD errata from an html page // ParseRemoteErrata grabs all of the OpenBSD errata from an html page
func ParseRemoteErrata(s string) (*Errati, error) { func ParseRemoteErrata(s string) (*Errata, error) {
resp, err := http.Get(s) resp, err := http.Get(s)
if err != nil { if err != nil {
return nil, err return nil, err
@ -152,29 +185,25 @@ func ParseRemoteErrata(s string) (*Errati, error) {
defer resp.Body.Close() defer resp.Body.Close()
return ParseErrata(resp.Body) return ParseErrata(resp.Body, s)
} }
// PrintErrata pretty prints an errata // PrintErrata pretty prints an errata
func PrintErrata(e *Errata) string { func PrintErrata(e *Erratum) string {
return fmt.Sprintf("%03d: %s: %s %s\n%s\n%s", return fmt.Sprintf("New OpenBSD Errata: %03d\n%s: %s\n%s",
e.ID, e.ID,
e.Type,
e.Date.String(), e.Date.String(),
e.Coverage,
e.Desc, e.Desc,
e.Link, e.Link,
) )
} }
// PrintErrataMD pretty prints an errata in markdown // PrintErrataMD pretty prints an errata in markdown
//func PrintErrataMD(e *Errata) string { func PrintErrataMD(e *Erratum) string {
// return fmt.Sprintf("#%03d: %s: %s _%s_\n%s\n[A source code patch exists which remedies this problem.](%s)", return fmt.Sprintf("# OpenBSD Errata %03d (_%s_)\n<pre>%s</pre>\n[A source code patch exists which remedies this problem.](%s)",
// e.ID, e.ID,
// e.Type, e.Date.Format("January 2, 2006"),
// e.Date.String(), e.Desc,
// e.Coverage, e.Link,
// e.Desc, )
// e.Link, }
// )
//}

View File

@ -5,12 +5,19 @@ import (
) )
func TestParseErrata(t *testing.T) { func TestParseErrata(t *testing.T) {
got, err := ParseRemoteErrata("https://www.openbsd.org/errata66.html") got, err := ParseRemoteErrata("http://ftp.openbsd.org/pub/OpenBSD/patches/6.6/common/")
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
l := len(got.List) l := len(got.List)
if l == 0 { if l < 1 {
t.Errorf("errata count %d; want > 0", l) t.Errorf("errata count %d; want > 1", l)
}
erratum := got.List[len(got.List)-1]
err = erratum.Fetch()
if err != nil {
t.Errorf("can't fetch data for erratum\n%s", err)
} }
} }

16
main.go
View File

@ -162,20 +162,26 @@ func main() {
errataCount, err = strconv.Atoi(storeCount) errataCount, err = strconv.Atoi(storeCount)
for { for {
got, err := ParseRemoteErrata( got, err := ParseRemoteErrata(
fmt.Sprintf("https://www.openbsd.org/errata%s.html", openbsdRelease), fmt.Sprintf("http://ftp.openbsd.org/pub/OpenBSD/patches/%s/common/", openbsdRelease),
) )
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
l := got.Length l := len(got.List)
if l > errataCount { if l > errataCount {
alertRooms, _ := store.Get("errata_rooms") alertRooms, _ := store.Get("errata_rooms")
c := 0 c := 0
for _, errata := range got.List { for _, erratum := range got.List {
if c+1 > errataCount { if c+1 > errataCount {
log.Printf("%03d: %s - %s\n", errata.ID, errata.Type, errata.Desc) log.Printf("Notifying for erratum %03d\n", erratum.ID)
err = erratum.Fetch()
if err != nil {
fmt.Println(err)
break
}
for _, room := range strings.Split(alertRooms, ",") { for _, room := range strings.Split(alertRooms, ",") {
cli.SendNotice(room, PrintErrata(&errata)) cli.SendNotice(room, PrintErrata(&erratum))
plugins.SendMD(cli, room, PrintErrataMD(&erratum))
} }
} }
c = c + 1 c = c + 1