Created
August 22, 2019 21:50
-
-
Save divolgin/bd5c3ac320476d0d16f4cdbcdd225a9c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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