141 lines
3.8 KiB
Go
141 lines
3.8 KiB
Go
package upstream
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"gitea-mail-templates/tools/logging"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
func CacheDir(root string) string { return filepath.Join(root, "build", "upstream") }
|
|
|
|
func ReadLock(root string) (Lock, error) {
|
|
var lock Lock
|
|
b, err := os.ReadFile(filepath.Join(root, "gitea.lock.json"))
|
|
if err != nil {
|
|
return lock, err
|
|
}
|
|
if err = json.Unmarshal(b, &lock); err != nil {
|
|
return lock, err
|
|
}
|
|
return lock, lock.validateIdentity()
|
|
}
|
|
|
|
func LoadPinned(root string) (*Snapshot, error) {
|
|
lock, err := ReadLock(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s, err := Load(CacheDir(root))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !reflect.DeepEqual(lock, s.Lock) {
|
|
return nil, fmt.Errorf("upstream cache differs from gitea.lock.json; run upstream prepare after reviewing the lock")
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// Prepare fetches immutable commit files only if the cache is absent. A corrupt
|
|
// cache is an error, not permission to silently replace local evidence.
|
|
func Prepare(ctx context.Context, root string) (*Snapshot, error) {
|
|
return prepareFrom(ctx, root, "https://raw.githubusercontent.com/go-gitea/gitea/")
|
|
}
|
|
|
|
func prepareFrom(ctx context.Context, root, rawBase string) (*Snapshot, error) {
|
|
lock, err := ReadLock(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err = os.Stat(filepath.Join(CacheDir(root), "lock.json")); err == nil {
|
|
return LoadPinned(root)
|
|
} else if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
client := &http.Client{Timeout: 45 * time.Second}
|
|
s := &Snapshot{Lock: lock, Files: map[string][]byte{}}
|
|
for name, hash := range lock.Files {
|
|
if !SafePath(name) {
|
|
return nil, fmt.Errorf("unsafe locked path %q", name)
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawBase+lock.Commit+"/"+name, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("User-Agent", "GiteaMailTemplates-locked-cache")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, readErr := io.ReadAll(io.LimitReader(resp.Body, 16<<20))
|
|
resp.Body.Close()
|
|
if readErr != nil {
|
|
return nil, readErr
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("%s: HTTP %d", name, resp.StatusCode)
|
|
}
|
|
if Digest(b) != hash {
|
|
return nil, fmt.Errorf("download checksum mismatch: %s", name)
|
|
}
|
|
s.Files[name] = b
|
|
}
|
|
if err = s.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
dir := CacheDir(root)
|
|
if err = os.MkdirAll(filepath.Dir(dir), 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
stage, err := os.MkdirTemp(filepath.Dir(dir), ".upstream-cache-")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer os.RemoveAll(stage)
|
|
for name, b := range s.Files {
|
|
target := filepath.Join(stage, filepath.FromSlash(name))
|
|
if err = os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = os.WriteFile(target, b, 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
b, _ := json.MarshalIndent(lock, "", " ")
|
|
if err = os.WriteFile(filepath.Join(stage, "lock.json"), append(b, '\n'), 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err = os.Stat(dir); err == nil {
|
|
return nil, fmt.Errorf("incomplete upstream cache exists at %s; inspect it before removing it", dir)
|
|
}
|
|
if err = os.Rename(stage, dir); err != nil {
|
|
return nil, err
|
|
}
|
|
logging.Info("Upstream", "Downloaded locked Gitea %s (%s) to build cache", lock.Tag, lock.Commit)
|
|
return LoadPinned(root)
|
|
}
|
|
|
|
func SyncRoot(ctx context.Context, tag, root string) error {
|
|
if err := os.MkdirAll(filepath.Join(root, "build"), 0755); err != nil {
|
|
return err
|
|
}
|
|
if err := Sync(ctx, tag, CacheDir(root)); err != nil {
|
|
return err
|
|
}
|
|
s, err := Load(CacheDir(root))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b, err := json.MarshalIndent(s.Lock, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(filepath.Join(root, "gitea.lock.json"), append(b, '\n'), 0644)
|
|
}
|