mirror.go

  1package jobs
  2
  3import (
  4	"context"
  5	"fmt"
  6	"path/filepath"
  7	"runtime"
  8
  9	"github.com/charmbracelet/log"
 10	"github.com/charmbracelet/soft-serve/git"
 11	"github.com/charmbracelet/soft-serve/server/backend"
 12	"github.com/charmbracelet/soft-serve/server/config"
 13	"github.com/charmbracelet/soft-serve/server/db"
 14	"github.com/charmbracelet/soft-serve/server/lfs"
 15	"github.com/charmbracelet/soft-serve/server/store"
 16	"github.com/charmbracelet/soft-serve/server/sync"
 17)
 18
 19func init() {
 20	Register("mirror-pull", mirrorPull{})
 21}
 22
 23type mirrorPull struct{}
 24
 25// Spec derives the spec used for pull mirrors and implements Runner.
 26func (m mirrorPull) Spec(ctx context.Context) string {
 27	cfg := config.FromContext(ctx)
 28	if cfg.Jobs.MirrorPull != "" {
 29		return cfg.Jobs.MirrorPull
 30	}
 31	return "@every 10m"
 32}
 33
 34// Func runs the (pull) mirror job task and implements Runner.
 35func (m mirrorPull) Func(ctx context.Context) func() {
 36	cfg := config.FromContext(ctx)
 37	logger := log.FromContext(ctx).WithPrefix("jobs.mirror")
 38	b := backend.FromContext(ctx)
 39	dbx := db.FromContext(ctx)
 40	datastore := store.FromContext(ctx)
 41	return func() {
 42		repos, err := b.Repositories(ctx)
 43		if err != nil {
 44			logger.Error("error getting repositories", "err", err)
 45			return
 46		}
 47
 48		// Divide the work up among the number of CPUs.
 49		wq := sync.NewWorkPool(ctx, runtime.GOMAXPROCS(0),
 50			sync.WithWorkPoolLogger(logger.Errorf),
 51		)
 52
 53		logger.Debug("updating mirror repos")
 54		for _, repo := range repos {
 55			if repo.IsMirror() {
 56				r, err := repo.Open()
 57				if err != nil {
 58					logger.Error("error opening repository", "repo", repo.Name(), "err", err)
 59					continue
 60				}
 61
 62				name := repo.Name()
 63				wq.Add(name, func() {
 64					repo := repo
 65					cmd := git.NewCommand("remote", "update", "--prune").WithContext(ctx)
 66					cmd.AddEnvs(
 67						fmt.Sprintf(`GIT_SSH_COMMAND=ssh -o UserKnownHostsFile="%s" -o StrictHostKeyChecking=no -i "%s"`,
 68							filepath.Join(cfg.DataPath, "ssh", "known_hosts"),
 69							cfg.SSH.ClientKeyPath,
 70						),
 71					)
 72
 73					if _, err := cmd.RunInDir(r.Path); err != nil {
 74						logger.Error("error running git remote update", "repo", name, "err", err)
 75					}
 76
 77					if cfg.LFS.Enabled {
 78						rcfg, err := r.Config()
 79						if err != nil {
 80							logger.Error("error getting git config", "repo", name, "err", err)
 81							return
 82						}
 83
 84						lfsEndpoint := rcfg.Section("lfs").Option("url")
 85						if lfsEndpoint == "" {
 86							// If there is no LFS url defined, means the repo
 87							// doesn't use LFS and we can skip it.
 88							return
 89						}
 90
 91						ep, err := lfs.NewEndpoint(lfsEndpoint)
 92						if err != nil {
 93							logger.Error("error creating LFS endpoint", "repo", name, "err", err)
 94							return
 95						}
 96
 97						client := lfs.NewClient(ep)
 98						if client == nil {
 99							logger.Errorf("failed to create lfs client: unsupported endpoint %s", lfsEndpoint)
100							return
101						}
102
103						if err := backend.StoreRepoMissingLFSObjects(ctx, repo, dbx, datastore, client); err != nil {
104							logger.Error("failed to store missing lfs objects", "err", err, "path", r.Path)
105							return
106						}
107					}
108				})
109			}
110		}
111
112		wq.Run()
113	}
114}