From 37233112b2ceb108155cf411ef738ea7e54f72d0 Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Mon, 24 Oct 2022 17:20:06 -0300 Subject: [PATCH] add a tool to check the status of a TUF server (#8428) Motivation: I often find myself digging through the XML in https://tuf.fleetctl.com to see if/when/what packages have been published in the different release channels, this tool aims to make the process less painful. ``` ~/fleet $ go run tools/tuf/status/status.go --help This is a CLI utility to fetch and filter the entries posted by a TUF repository. -key-filter string filter keys using a regular expression (default "stable") -url string URL of the TUF repository (default "https://tuf.fleetctl.com") ``` ### Examples - To filter all items on the edge channel use `--key-filter="edge"` - To filter all items on version `1.3` including patches that run on Linux use `--key-filter="linux/1.3.*"` - To filter Fleet Desktop items on `1.3.*`, `stable` and `edge` that run on macOS use `--key-filter="desktop/*.*/macos/(1.3.*|stable|edge)"` ### Example output ``` ~/fleet $ go run tools/tuf/status/status.go --key-filter="desktop/*.*/macos/(1.3.*|stable|edge)" Results filtered by "desktop/*.*/macos/(1.3.*|stable|edge)" and sorted by version, platform and key. VERSION PLATFORM KEY LAST MODIFIED SIZE ETAG 1.3 macos targets/desktop/macos/1.3/desktop.app.tar.gz 2022-10-20T00:42:58.000Z 28.4 MB "ceb7a975880db6ab9aea7907915406e3-4" 1.3.0 macos targets/desktop/macos/1.3.0/desktop.app.tar.gz 2022-10-20T00:42:58.000Z 28.4 MB "ceb7a975880db6ab9aea7907915406e3-4" 1.3.1 macos targets/desktop/macos/1.3.1/desktop.app.tar.gz 2022-10-21T20:28:24.000Z 28.5 MB "3c6641a0df00009f3f076bd6b4fbc748-4" edge macos targets/desktop/macos/edge/desktop.app.tar.gz 2022-10-21T20:28:37.000Z 28.5 MB "3c6641a0df00009f3f076bd6b4fbc748-4" stable macos targets/desktop/macos/stable/desktop.app.tar.gz 2022-10-20T00:43:06.000Z 28.4 MB "ceb7a975880db6ab9aea7907915406e3-4" ``` --- tools/tuf/status/README.md | 19 +++++ tools/tuf/status/status.go | 139 +++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 tools/tuf/status/README.md create mode 100644 tools/tuf/status/status.go diff --git a/tools/tuf/status/README.md b/tools/tuf/status/README.md new file mode 100644 index 0000000000..f64b76ec69 --- /dev/null +++ b/tools/tuf/status/README.md @@ -0,0 +1,19 @@ +# TUF status + +``` +~/fleet $ go run tools/tuf/status/status.go --help + +This is a CLI utility to fetch and filter the entries posted by a TUF repository. + + -key-filter string + filter keys using a regular expression (default "stable") + -url string + URL of the TUF repository (default "https://tuf.fleetctl.com") + + +Examples + +- To filter all items on the edge channel use --key-filter="edge" +- To filter all items on version 1.3 including patches that run on Linux use --key-filter="linux/1.3.*" +- To filter Fleet Desktop items on 1.3.*, stable and edge that run on macOS use --key-filter="desktop/*.*/macos/(1.3.*|stable|edge)" +``` diff --git a/tools/tuf/status/status.go b/tools/tuf/status/status.go new file mode 100644 index 0000000000..6a8d304bd7 --- /dev/null +++ b/tools/tuf/status/status.go @@ -0,0 +1,139 @@ +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "io" + "net/http" + "os" + "regexp" + "sort" + "strings" + + "github.com/olekukonko/tablewriter" +) + +type listBucketResult struct { + XMLName xml.Name `xml:"ListBucketResult"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + Name string `xml:"Name"` + Prefix string `xml:"Prefix"` + Marker string `xml:"Marker"` + MaxKeys string `xml:"MaxKeys"` + IsTruncated string `xml:"IsTruncated"` + Contents []content `xml:"Contents"` +} + +type content struct { + Text string `xml:",chardata"` + Key string `xml:"Key"` + LastModified string `xml:"LastModified"` + ETag string `xml:"ETag"` + Size int64 `xml:"Size"` + StorageClass string `xml:"StorageClass"` +} + +func main() { + flag.CommandLine.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), ` +This is a CLI utility to fetch and filter the entries posted by a TUF repository. + +`) + flag.CommandLine.PrintDefaults() + fmt.Fprintf(flag.CommandLine.Output(), ` + +Examples + +- To filter all items on the edge channel use --key-filter="edge" +- To filter all items on version 1.3 including patches that run on Linux use --key-filter="linux/1.3.*" +- To filter Fleet Desktop items on 1.3.*, stable and edge that run on macOS use --key-filter="desktop/*.*/macos/(1.3.*|stable|edge)" + +`) + } + filter := flag.String("key-filter", "stable", "filter keys using a regular expression") + url := flag.String("url", "https://tuf.fleetctl.com", "URL of the TUF repository") + flag.Parse() + + res, err := http.Get(*url) + if err != nil { + panic(err) + } + + body, err := io.ReadAll(res.Body) + if err != nil { + panic(err) + } + defer res.Body.Close() + + var list listBucketResult + if err := xml.Unmarshal(body, &list); err != nil { + panic(err) + } + + if err := printTable(list.Contents, *filter); err != nil { + panic(err) + } +} + +func printTable(contents []content, filter string) error { + data := [][]string{} + regFilter, err := regexp.Compile(filter) + if err != nil { + return err + } + + for _, content := range contents { + if regFilter.MatchString(content.Key) { + r := strings.Split(content.Key, "/") + platform, version := r[2], r[3] + data = append(data, []string{version, platform, content.Key, content.LastModified, byteCountSI(content.Size), content.ETag}) + } + } + + // sort by version, platform, key + sort.Slice(data, func(i, j int) bool { + if data[i][0] != data[j][0] { + return data[i][0] < data[j][0] + } + + if data[i][1] != data[j][1] { + return data[i][1] < data[j][1] + } + + return data[i][2] < data[j][2] + }) + + fmt.Printf("\nResults filtered by \"%s\" and sorted by version, platform and key.\n\n", filter) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"version", "platform", "key", "last modified", "size", "etag"}) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetTablePadding("\t") + table.SetNoWhiteSpace(true) + table.AppendBulk(data) + table.Render() + return nil +} + +func byteCountSI(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +}