constellation/internal/versions/hash-generator/generate.go
Paul Meyer 4bc191e434 versions: move hash generator into own package
Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com>
2023-01-11 14:29:32 +01:00

173 lines
3.5 KiB
Go

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package main
import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"io"
"log"
"net/http"
"os"
"golang.org/x/tools/go/ast/astutil"
)
func mustGetHash(url string) string {
// remove quotes around url
url = url[1 : len(url)-1]
// Get the data
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
panic(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
panic("bad status: " + resp.Status)
}
// Generate SHA256 hash of the file
sha := sha256.New()
if _, err := io.Copy(sha, resp.Body); err != nil {
panic(err)
}
fileHash := sha.Sum(nil)
// Get upstream hash
req, err = http.NewRequestWithContext(context.Background(), http.MethodGet, url+".sha256", http.NoBody)
if err != nil {
panic(err)
}
resp, err = http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Check server response
if resp.StatusCode != http.StatusOK {
panic("bad status: " + resp.Status)
}
// Compare hashes
// Take the first 64 ascii characters = 32 bytes.
// Some .sha256 files contain additional information afterwards.
upstreamHash := make([]byte, 64)
if _, err = resp.Body.Read(upstreamHash); err != nil {
panic(err)
}
if string(upstreamHash) != fmt.Sprintf("%x", fileHash) {
panic("hash mismatch")
}
return fmt.Sprintf("\"sha256:%x\"", fileHash)
}
func main() {
fmt.Println("Generating hashes...")
const filePath = "./versions.go"
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
var componentListsCtr, componentCtr int
newFile := astutil.Apply(file, func(cursor *astutil.Cursor) bool {
n := cursor.Node()
//
// Find CompositeLit of type 'components.Components'
//
comp, ok := n.(*ast.CompositeLit)
if !ok {
return true
}
selExpr, ok := comp.Type.(*ast.SelectorExpr)
if !ok {
return true
}
if selExpr.Sel.Name != "Components" {
return true
}
xIdent, ok := selExpr.X.(*ast.Ident)
if !ok {
return true
}
if xIdent.Name != "components" {
return true
}
componentListsCtr++
//
// Iterate over the components
//
for _, componentElt := range comp.Elts {
component := componentElt.(*ast.CompositeLit)
componentCtr++
var url *ast.KeyValueExpr
var hash *ast.KeyValueExpr
for _, e := range component.Elts {
kv, ok := e.(*ast.KeyValueExpr)
if !ok {
continue
}
ident, ok := kv.Key.(*ast.Ident)
if !ok {
continue
}
switch ident.Name {
case "URL":
url = kv
case "Hash":
hash = kv
}
}
fmt.Println("Generating hash for", url.Value.(*ast.BasicLit).Value)
hash.Value.(*ast.BasicLit).Value = mustGetHash(url.Value.(*ast.BasicLit).Value)
}
return true
}, nil,
)
var buf bytes.Buffer
printConfig := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
if err = printConfig.Fprint(&buf, fset, newFile); err != nil {
log.Fatalf("error formatting file %s: %s", filePath, err)
}
if err := os.WriteFile(filePath, buf.Bytes(), 0o644); err != nil {
log.Fatalf("error writing file %s: %s", filePath, err)
}
if componentCtr == 0 {
log.Fatalf("no components lists found")
}
fmt.Printf("Successfully generated hashes for %d components in %d component lists.\n", componentCtr, componentListsCtr)
}