hooks.go

  1package backend
  2
  3import (
  4	"context"
  5	"io"
  6	"os"
  7	"sync"
  8
  9	"github.com/charmbracelet/soft-serve/git"
 10	"github.com/charmbracelet/soft-serve/pkg/hooks"
 11	"github.com/charmbracelet/soft-serve/pkg/proto"
 12	"github.com/charmbracelet/soft-serve/pkg/sshutils"
 13	"github.com/charmbracelet/soft-serve/pkg/webhook"
 14)
 15
 16var _ hooks.Hooks = (*Backend)(nil)
 17
 18// PostReceive is called by the git post-receive hook.
 19//
 20// It implements Hooks.
 21func (d *Backend) PostReceive(_ context.Context, _ io.Writer, _ io.Writer, repo string, args []hooks.HookArg) {
 22	d.logger.Debug("post-receive hook called", "repo", repo, "args", args)
 23}
 24
 25// PreReceive is called by the git pre-receive hook.
 26//
 27// It implements Hooks.
 28func (d *Backend) PreReceive(_ context.Context, _ io.Writer, _ io.Writer, repo string, args []hooks.HookArg) {
 29	d.logger.Debug("pre-receive hook called", "repo", repo, "args", args)
 30}
 31
 32// Update is called by the git update hook.
 33//
 34// It implements Hooks.
 35func (d *Backend) Update(ctx context.Context, _ io.Writer, _ io.Writer, repo string, arg hooks.HookArg) {
 36	d.logger.Debug("update hook called", "repo", repo, "arg", arg)
 37
 38	// Find user
 39	var user proto.User
 40	if pubkey := os.Getenv("SOFT_SERVE_PUBLIC_KEY"); pubkey != "" { //nolint:nestif
 41		pk, _, err := sshutils.ParseAuthorizedKey(pubkey)
 42		if err != nil {
 43			d.logger.Error("error parsing public key", "err", err)
 44			return
 45		}
 46
 47		user, err = d.UserByPublicKey(ctx, pk)
 48		if err != nil {
 49			d.logger.Error("error finding user from public key", "key", pubkey, "err", err)
 50			return
 51		}
 52	} else if username := os.Getenv("SOFT_SERVE_USERNAME"); username != "" {
 53		var err error
 54		user, err = d.User(ctx, username)
 55		if err != nil {
 56			d.logger.Error("error finding user from username", "username", username, "err", err)
 57			return
 58		}
 59	} else {
 60		d.logger.Error("error finding user")
 61		return
 62	}
 63
 64	// Get repo
 65	r, err := d.Repository(ctx, repo)
 66	if err != nil {
 67		d.logger.Error("error finding repository", "repo", repo, "err", err)
 68		return
 69	}
 70
 71	// TODO: run this async
 72	// This would probably need something like an RPC server to communicate with the hook process.
 73	if git.IsZeroHash(arg.OldSha) || git.IsZeroHash(arg.NewSha) {
 74		wh, err := webhook.NewBranchTagEvent(ctx, user, r, arg.RefName, arg.OldSha, arg.NewSha)
 75		if err != nil {
 76			d.logger.Error("error creating branch_tag webhook", "err", err)
 77		} else if err := webhook.SendEvent(ctx, wh); err != nil {
 78			d.logger.Error("error sending branch_tag webhook", "err", err)
 79		}
 80	}
 81	wh, err := webhook.NewPushEvent(ctx, user, r, arg.RefName, arg.OldSha, arg.NewSha)
 82	if err != nil {
 83		d.logger.Error("error creating push webhook", "err", err)
 84	} else if err := webhook.SendEvent(ctx, wh); err != nil {
 85		d.logger.Error("error sending push webhook", "err", err)
 86	}
 87}
 88
 89// PostUpdate is called by the git post-update hook.
 90//
 91// It implements Hooks.
 92func (d *Backend) PostUpdate(ctx context.Context, _ io.Writer, _ io.Writer, repo string, args ...string) {
 93	d.logger.Debug("post-update hook called", "repo", repo, "args", args)
 94
 95	var wg sync.WaitGroup
 96
 97	// Populate last-modified file.
 98	wg.Add(1)
 99	go func() {
100		defer wg.Done()
101		if err := populateLastModified(ctx, d, repo); err != nil {
102			d.logger.Error("error populating last-modified", "repo", repo, "err", err)
103			return
104		}
105	}()
106
107	wg.Wait()
108}
109
110func populateLastModified(ctx context.Context, d *Backend, name string) error {
111	var rr *repo
112	_rr, err := d.Repository(ctx, name)
113	if err != nil {
114		return err
115	}
116
117	if r, ok := _rr.(*repo); ok {
118		rr = r
119	} else {
120		return proto.ErrRepoNotFound
121	}
122
123	r, err := rr.Open()
124	if err != nil {
125		return err
126	}
127
128	c, err := r.LatestCommitTime()
129	if err != nil {
130		return err //nolint:wrapcheck
131	}
132
133	return rr.writeLastModified(c)
134}