Thanks for this inspiring thread @TotallyInformation and @pgr as the original starter.
I thought it would be a nice practice for my Go skills to see if I can make a similar program in Go to easily update Hugo from the command line. I’ve take a bit a different road, and used the GitHub API to get a JSON file with all the latest releases (instead of parsing the webpage).
I also didn’t want to overwrite the original hugo.exe
file, in case there was some new feature that broke the website and I wanted to roll-back. So instead I rename the ‘old’ Hugo file.
Here’s how it looks like:
C:\Bestanden\Go practice\Networking>go run update-hugo.go
2018/06/20 17:42:02 Downloading Hugo v0.42.1, released on 13 Jun 2018 10:21
2018/06/20 17:42:06 Updated Hugo. Earlier version is stored as hugo_previous_2018-06-20_1742.exe
This is the code:
package main
import (
"archive/zip"
"bytes"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"time"
)
// hugoRelease models the relevant JSON data returned by the GitHub API
type hugoRelease struct {
Version string `json:"tag_name"`
ReleaseDate time.Time `json:"published_at"`
Assets []struct {
URL string `json:"browser_download_url"`
}
}
func main() {
// Set this variable to your platform.
// See the download URLs on https://github.com/gohugoio/hugo/releases/latest for how Hugo names your operating system's download URL
platformName := "Windows-64bit"
// Set this path to whichever folder that is on your PATH and that you want to store the new Hugo version in
targetFolder := `C:\Hugo\`
// Set the Go's app working directory to that folder (saves us having to provide the path in full each time)
if err := os.Chdir(targetFolder); err != nil {
log.Fatalf("Failed to set program's working directory to %s", targetFolder)
}
// STEP 1. Get release information for the latest Hugo release through the GitHub API
request, err := http.Get("https://api.github.com/repos/gohugoio/hugo/releases/latest")
if err != nil {
log.Fatalf("Failed to fetch the GitHub API response. Error: %s", err)
}
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Fatalf("Failed to read the returned GitHub API response. Error: %s", err)
}
// Unmarshal the downloaded JSON data onto our struct
release := hugoRelease{}
if err = json.Unmarshal(body, &release); err != nil {
log.Fatalf("Failed to marshal JSON onto our struct object. Error: %s", err)
}
log.Printf("Downloading Hugo %s, released on %s", release.Version,
release.ReleaseDate.Format("2 Jan 2006 15:04"))
// To get the right download URL we loop over all URLs, looking for the one that contains our platform
downloadURL := ""
for _, v := range release.Assets {
if strings.Contains(v.URL, platformName) {
downloadURL = v.URL
break
}
}
// STEP 2. Now that we have the download URL, download the file from GitHub
resp, err := http.Get(downloadURL)
if err != nil {
log.Fatal("GitHub cannot be reached")
} else if resp.StatusCode >= 400 {
log.Fatalf("Failed to get %q: HTTP error %d", downloadURL, resp.StatusCode)
}
defer resp.Body.Close()
// STEP 3. Rename current Hugo executable so we can rollback to our current Hugo version easily
previousFile := "hugo_previous_" + time.Now().Format("2006-01-02_1504") + ".exe"
err = os.Rename("hugo.exe", previousFile)
if err != nil {
log.Fatalf("Failed to rename existing Hugo executable. Error: %s", err)
}
// STEP 4. Unzip downloaded file and extract Hugo executable
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Failed to read downloaded GitHub file")
}
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
log.Fatalf("Failed to read zip file")
}
// Go through zip file, and look for hugo.exe. We don't need to uncompress LICENSE nor README.md
for _, f := range zipReader.File {
if f.Name != "hugo.exe" {
continue
}
fileReader, err := f.Open()
if err != nil {
log.Fatalln("Failed to open the compressed hugo.exe file")
}
defer fileReader.Close()
targetFile, err := os.Create("hugo.exe")
if err != nil {
log.Fatalln("Failed to open the target hugo.exe file")
}
defer targetFile.Close()
// Copy hugo.exe from the zip into the target file
if _, err := io.Copy(targetFile, fileReader); err != nil {
log.Fatalln("Failed to write hugo.exe from the zip into its target file")
}
break // Terminate loop; we've found our hugo.exe file and extracted it
}
log.Printf("Updated Hugo. Earlier version is stored as %s", previousFile)
}
I’ll write up a blog post with more information. When that’s live, I’ll post the link here.