Skip to content

Instantly share code, notes, and snippets.

@divolgin
Created August 22, 2019 21:50
Show Gist options
  • Save divolgin/bd5c3ac320476d0d16f4cdbcdd225a9c to your computer and use it in GitHub Desktop.
Save divolgin/bd5c3ac320476d0d16f4cdbcdd225a9c to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"text/template"
)
func main() {
if len(os.Args) < 3 {
fmt.Printf("Usage: %s [repo] [template]\n", os.Args[0])
os.Exit(-1)
}
github := os.Args[1]
templateFile := os.Args[2]
templateBytes, err := ioutil.ReadFile(templateFile)
if err != nil {
panic(err)
}
owner, repo, err := parseRepo(github)
if err != nil {
panic(err)
}
release, err := getLatestRelease(owner, repo)
if err != nil {
panic(err)
}
checksums, err := downloadChecksums(release)
if err != nil {
panic(err)
}
funcMap := makeFuncMap(release, checksums)
tmpl, err := template.New("krew-manifest").Funcs(funcMap).Parse(string(templateBytes))
if err != nil {
panic(err)
}
if err := tmpl.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
// poor man's parser for "github.com/owner/repo"
func parseRepo(github string) (owner string, repo string, err error) {
parts := strings.Split(github, "/")
if len(parts) != 3 {
err = fmt.Errorf("expected format github.com/owner/repo: %s", github)
return
}
owner = parts[1]
repo = parts[2]
return
}
func getLatestRelease(owner, repo string) (*Release, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
body, err := get(url)
if err != nil {
return nil, err
}
r := &Release{}
if err := json.Unmarshal(body, r); err != nil {
return nil, err
}
return r, nil
}
func downloadChecksums(release *Release) (map[string]string, error) {
var asset *Asset
for _, a := range release.Assets {
if !strings.Contains(a.ContentType, "text/plain") {
continue
}
if a.State != "uploaded" {
continue
}
if !strings.Contains(a.Name, "checksums") {
continue
}
asset = &a
break
}
body, err := get(asset.BrowserDownloadURL)
if err != nil {
return nil, err
}
checksums := make(map[string]string)
scanner := bufio.NewScanner(bytes.NewReader(body))
for scanner.Scan() {
line := scanner.Text()
checksums[strings.TrimSpace(line[64:])] = line[:64]
}
return checksums, nil
}
func get(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("unexpected status code from %s: %v", url, resp.Status)
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
}
type TemplateCtx struct {
Release *Release
Checksums map[string]string
}
func makeFuncMap(release *Release, checksums map[string]string) template.FuncMap {
ctx := &TemplateCtx{
Release: release,
Checksums: checksums,
}
funcMap := template.FuncMap{
"SHA256": ctx.sha256,
"DownloadURL": ctx.downloadURL,
"TagName": func() string { return release.TagName },
}
return funcMap
}
func (ctx *TemplateCtx) sha256(project, platform string) string {
asset := ctx.findAsset(project, platform)
if asset == nil {
return "<asset not found>"
}
return ctx.Checksums[asset.Name]
}
func (ctx *TemplateCtx) downloadURL(project, platform string) string {
asset := ctx.findAsset(project, platform)
if asset == nil {
return "<asset not found>"
}
return asset.BrowserDownloadURL
}
func (ctx *TemplateCtx) findAsset(project, platform string) *Asset {
for _, a := range ctx.Release.Assets {
if strings.Contains(a.Name, project) && strings.Contains(a.Name, platform) {
return &a
}
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment