package main import ( "errors" "fmt" "io" "log" "net/http" "os" "regexp" "strings" "github.com/coreos/go-semver/semver" "golang.org/x/net/html" ) type FileInfo struct { FileName string Regex string Replace string } func (f FileInfo) Update() error { b, err := os.ReadFile(f.FileName) if err != nil { return err } re := regexp.MustCompile(f.Regex) newContents := re.ReplaceAll(b, []byte(f.Replace)) err = os.WriteFile(f.FileName, newContents, 0640) if err != nil { return err } return nil } // removePatch cleans version from "1.20.1" to "1.20" (think go.mod entry) func removePatch(version string) string { verInfo := semver.New(version) return fmt.Sprintf("%d.%d", verInfo.Major, verInfo.Minor) } // findHash will search the downloads table for the hashes matching the artifacts list func findHashes(body io.Reader, version string) (map[string]string, error) { htmlTokens := html.NewTokenizer(body) artifacts := []string{ fmt.Sprintf("go%s.linux-amd64.tar.gz", version), fmt.Sprintf("go%s.darwin-arm64.tar.gz", version), fmt.Sprintf("go%s.darwin-amd64.tar.gz", version), } var insideDownloadTable bool var currentRow string hashes := make(map[string]string) for { tokenType := htmlTokens.Next() // if it's an error token, we either reached // the end of the file, or the HTML was malformed if tokenType == html.ErrorToken { err := htmlTokens.Err() if errors.Is(err, io.EOF) { // end of the file, break out of the loop break } return nil, htmlTokens.Err() } if tokenType == html.StartTagToken { // get the token token := htmlTokens.Token() if token.Data == "table" && len(token.Attr) == 1 && token.Attr[0].Val == "downloadtable" { insideDownloadTable = true } if insideDownloadTable && token.Data == "a" && len(token.Attr) == 2 { for _, f := range artifacts { // Check if the current row matches a desired file if strings.Contains(token.Attr[1].Val, f) { currentRow = f break } } } if currentRow != "" && token.Data == "tt" { // the next token should be the page title tokenType = htmlTokens.Next() // just make sure it's actually a text token if tokenType == html.TextToken { hashes[currentRow] = htmlTokens.Token().Data currentRow = "" } } } // Found a hash for each filename if len(hashes) == len(artifacts) { break } // Reached end of table if tokenType == html.EndTagToken && htmlTokens.Token().Data == "table" { if len(hashes) == 0 { return nil, fmt.Errorf("could not find version %q on downloads page", version) } return nil, fmt.Errorf("only found %d hashes expected %d: %v", len(hashes), len(artifacts), hashes) } } return hashes, nil } func getHashes(version string) (map[string]string, error) { resp, err := http.Get(`https://go.dev/dl/`) if err != nil { return nil, err } defer resp.Body.Close() return findHashes(resp.Body, version) } func main() { version := os.Args[1] // Handle situation user accidentally provides version as "v1.19.2" if strings.HasPrefix(version, "v") { version = strings.TrimLeft(version, "v") } hashes, err := getHashes(version) if err != nil { log.Fatal(err) } for file, hash := range hashes { fmt.Printf("%s %s\n", hash, file) } noPatchVersion := removePatch(version) files := []FileInfo{ { FileName: ".circleci/config.yml", Regex: `(quay\.io\/influxdb\/telegraf-ci):(\d.\d*.\d)`, Replace: "$1:" + version, }, { FileName: "go.mod", Regex: `(go)\s(\d.\d*)`, Replace: "$1 " + noPatchVersion, }, { FileName: "Makefile", Regex: `(quay\.io\/influxdb\/telegraf-ci):(\d.\d*.\d)`, Replace: "$1:" + version, }, { FileName: "README.md", Regex: `(Telegraf requires Go version) (\d.\d*)`, Replace: "$1 " + noPatchVersion, }, { FileName: "scripts/ci.docker", Regex: `(FROM golang):(\d.\d*.\d)`, Replace: "$1:" + version, }, { FileName: "scripts/installgo_linux.sh", Regex: `(GO_VERSION)=("\d.\d*.\d")`, Replace: fmt.Sprintf("$1=%q", version), }, { FileName: "scripts/installgo_mac.sh", Regex: `(GO_VERSION)=("\d.\d*.\d")`, Replace: fmt.Sprintf("$1=%q", version), }, { FileName: "scripts/installgo_windows.sh", Regex: `(GO_VERSION)=("\d.\d*.\d")`, Replace: fmt.Sprintf("$1=%q", version), }, { FileName: "scripts/installgo_linux.sh", Regex: `(GO_VERSION_SHA)=".*"`, Replace: fmt.Sprintf("$1=%q", hashes[fmt.Sprintf("go%s.linux-amd64.tar.gz", version)]), }, { FileName: "scripts/installgo_mac.sh", Regex: `(GO_VERSION_SHA_arm64)=".*"`, Replace: fmt.Sprintf("$1=%q", hashes[fmt.Sprintf("go%s.darwin-arm64.tar.gz", version)]), }, { FileName: "scripts/installgo_mac.sh", Regex: `(GO_VERSION_SHA_amd64)=".*"`, Replace: fmt.Sprintf("$1=%q", hashes[fmt.Sprintf("go%s.darwin-amd64.tar.gz", version)]), }, { FileName: ".github/workflows/readme-linter.yml", Regex: `(go-version): '\d.\d*.\d'`, Replace: fmt.Sprintf("$1: '%s'", version), }, } for _, f := range files { fmt.Printf("Updating %s \n", f.FileName) err := f.Update() if err != nil { log.Panic(err) } } }