devtools/src/jehanne/cmd/vendor/vendor.go

249 lines
4.7 KiB
Go

package main
import (
"archive/tar"
"bytes"
"compress/bzip2"
"compress/gzip"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"flag"
"hash"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"strings"
)
const (
ignore = "*\n!.gitignore\n"
dirPermissions = 0755
)
type V struct {
Upstream string
Digest map[string]string
Compress string
RemovePrefix bool
Exclude []string
}
func main() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
justCheck := flag.Bool("check", false, "verify the code in upstream/")
flag.Parse()
if(*justCheck && !repositoryIsClean()){
log.Fatal("cannot verify upstream/ files: working directory not clean")
}
f, err := ioutil.ReadFile("vendor.json")
if err != nil {
log.Fatal(err)
}
vendor := &V{}
if err := json.Unmarshal(f, vendor); err != nil {
log.Fatal(err)
}
if _, err := os.Stat("upstream"); err == nil {
log.Println("recreating upstream")
if(*justCheck){
run("rm", "-r", "-f", "upstream")
} else {
run("git", "rm", "-r", "-f", "upstream")
}
} else {
if(*justCheck){
log.Fatalf("Cannot verify upstream/ as it does not exists.")
}
os.MkdirAll("patch", dirPermissions)
os.MkdirAll("jehanne", dirPermissions)
ig, err := os.Create(path.Join("jehanne", ".gitignore"))
if err != nil {
log.Fatal(err)
}
defer ig.Close()
if _, err := ig.WriteString(ignore); err != nil {
log.Fatal(err)
}
run("git", "add", ig.Name())
}
if err := do(vendor, *justCheck); err != nil {
log.Fatal(err)
}
if(*justCheck){
if(repositoryIsClean()){
log.Printf("the files in upstream/ matches those in "+path.Base(vendor.Upstream))
} else {
log.Fatalf("the files in upstream/ does not match those in "+path.Base(vendor.Upstream))
}
} else {
run("git", "add", "vendor.json")
run("git", "commit", "-s", "-m", "vendor: pull in "+path.Base(vendor.Upstream))
}
}
func repositoryIsClean() bool {
out, err := exec.Command("git", "status", "--porcelain").Output()
if err != nil {
log.Fatal(err)
}
if(len(out) > 0){
log.Println("git status --porcelain");
log.Println(string(out))
return false
}
return true
}
func do(v *V, justCheck bool) error {
name := fetch(v)
f, err := os.Open(name)
if err != nil {
return err
}
defer os.Remove(name)
var unZ io.Reader
switch v.Compress {
case "gzip":
unZ, err = gzip.NewReader(f)
if err != nil {
return err
}
case "bzip2":
unZ = bzip2.NewReader(f)
default:
unZ = f
}
ar := tar.NewReader(unZ)
h, err := ar.Next()
untar:
for ; err == nil; h, err = ar.Next() {
n := h.Name
if v.RemovePrefix {
n = strings.SplitN(n, "/", 2)[1]
}
for _, ex := range v.Exclude {
if strings.HasPrefix(n, ex) {
continue untar
}
}
n = path.Join("upstream", n)
if h.FileInfo().IsDir() {
os.MkdirAll(n, dirPermissions)
continue
}
os.MkdirAll(path.Dir(n), dirPermissions)
out, err := os.Create(n)
if err != nil {
log.Println(err)
continue
}
if n, err := io.Copy(out, ar); n != h.Size || err != nil {
return err
}
out.Close()
}
if err != io.EOF {
return err
}
if(justCheck){
return nil;
}
return run("git", "add", "upstream")
}
type match struct {
hash.Hash
Good []byte
Name string
}
func (m match) OK() bool {
return bytes.Equal(m.Good, m.Hash.Sum(nil))
}
func fetch(v *V) string {
if len(v.Digest) == 0 {
log.Fatal("no checksums specifed")
}
f, err := ioutil.TempFile("", "cmdVendor")
if err != nil {
log.Fatal(err)
}
defer f.Close()
req, err := http.NewRequest("GET", v.Upstream, nil)
if err != nil {
log.Fatal(err)
}
client := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DisableCompression: true,
},
}
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var digests []match
for k, v := range v.Digest {
g, err := hex.DecodeString(v)
if err != nil {
log.Fatal(err)
}
switch k {
case "sha224":
digests = append(digests, match{sha256.New224(), g, k})
case "sha256":
digests = append(digests, match{sha256.New(), g, k})
case "sha384":
digests = append(digests, match{sha512.New384(), g, k})
case "sha512":
digests = append(digests, match{sha512.New(), g, k})
}
}
ws := make([]io.Writer, len(digests))
for i := range digests {
ws[i] = digests[i]
}
w := io.MultiWriter(ws...)
if _, err := io.Copy(f, io.TeeReader(res.Body, w)); err != nil {
log.Fatal(err)
}
for _, h := range digests {
if !h.OK() {
log.Fatalf("mismatched %q hash\n\tWanted %x\n\tGot %x\n", h.Name, h.Good, h.Hash.Sum(nil))
}
}
return f.Name()
}
func run(exe string, arg ...string) error {
cmd := exec.Command(exe, arg...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}