internal/control/deps.go

ff759b53942049b2043e132f5482b442a7265b98
gitbay/internal/control/deps.go history · blame · raw

133 lines · 4056 bytes

  1package control
  2
  3import (
  4	"errors"
  5	"fmt"
  6	"io"
  7
  8	"gitbay.org/gitbay/internal/policy"
  9	"gitbay.org/gitbay/internal/protocol"
 10	"gitbay.org/gitbay/internal/store"
 11)
 12
 13func init() {
 14	// Dependency update checks. Opt-in per repository: the check tells a
 15	// public registry what the repository depends on, which is the owner's
 16	// disclosure to make, not the instance's.
 17	register(Command{Path: []string{"repo", "deps", "enable"},
 18		Summary:  "check dependencies for updates",
 19		Usage:    "repo deps enable <owner/name>",
 20		Examples: []string{"repo deps enable krz/gitbay"},
 21		Run:      runDepsEnable})
 22	register(Command{Path: []string{"repo", "deps", "disable"},
 23		Summary:  "stop checking dependencies",
 24		Usage:    "repo deps disable <owner/name>",
 25		Examples: []string{"repo deps disable krz/gitbay"},
 26		Run:      runDepsDisable})
 27	register(Command{Path: []string{"repo", "deps", "status"},
 28		Summary:  "show dependency check state",
 29		Usage:    "repo deps status <owner/name>",
 30		Examples: []string{"repo deps status krz/gitbay"},
 31		ReadOnly: true, Run: runDepsStatus})
 32}
 33
 34// DepsOut is what `repo deps status` emits: the check's state and what
 35// the last run found behind. Named so the web decodes the same struct
 36// the command writes.
 37type DepsOut struct {
 38	Enabled     bool        `json:"enabled"`
 39	LastCheck   string      `json:"last_check,omitempty"`
 40	LastError   string      `json:"last_error,omitempty"`
 41	IssueNumber int64       `json:"issue_number,omitempty"`
 42	Behind      []DepBehind `json:"behind"`
 43}
 44
 45// DepBehind is one dependency with a newer release than the manifest pins.
 46type DepBehind struct {
 47	Ecosystem string `json:"ecosystem"`
 48	Name      string `json:"name"`
 49	Current   string `json:"current"`
 50	Latest    string `json:"latest"`
 51}
 52
 53func runDepsEnable(c *Ctx, args []string) int {
 54	if len(args) != 1 {
 55		return c.usage()
 56	}
 57	repo, code := resolveRepo(c, args[0], policy.CanAdmin)
 58	if code >= 0 {
 59		return code
 60	}
 61	if err := c.Store.EnableDepCheck(repo.ID); err != nil {
 62		return c.fail(protocol.ExitFailure, "%v", err)
 63	}
 64	return c.emit(map[string]any{"enabled": true}, func(w io.Writer) {
 65		fmt.Fprintf(w, "dependency checks enabled on %s\n", repo.Path())
 66	})
 67}
 68
 69func runDepsDisable(c *Ctx, args []string) int {
 70	if len(args) != 1 {
 71		return c.usage()
 72	}
 73	repo, code := resolveRepo(c, args[0], policy.CanAdmin)
 74	if code >= 0 {
 75		return code
 76	}
 77	if err := c.Store.DisableDepCheck(repo.ID); err != nil {
 78		return c.fail(protocol.ExitFailure, "%v", err)
 79	}
 80	return c.emit(map[string]any{"enabled": false}, func(w io.Writer) {
 81		fmt.Fprintf(w, "dependency checks disabled on %s\n", repo.Path())
 82	})
 83}
 84
 85func runDepsStatus(c *Ctx, args []string) int {
 86	if len(args) != 1 {
 87		return c.usage()
 88	}
 89	repo, code := resolveRepo(c, args[0], policy.CanRead)
 90	if code >= 0 {
 91		return code
 92	}
 93	check, err := c.Store.DepCheckFor(repo.ID)
 94	if errors.Is(err, store.ErrNotFound) {
 95		return c.emit(map[string]any{"enabled": false}, func(w io.Writer) {
 96			v := c.view(w)
 97			v.fields("checks", fmt.Sprintf("off (repo deps enable %s)", repo.Path()))
 98		})
 99	}
100	if err != nil {
101		return c.fail(protocol.ExitFailure, "%v", err)
102	}
103	reports, err := c.Store.ReportedDeps(repo.ID)
104	if err != nil {
105		return c.fail(protocol.ExitFailure, "%v", err)
106	}
107	out := DepsOut{Enabled: true, LastCheck: check.LastCheck, LastError: check.LastError,
108		IssueNumber: check.IssueNumber, Behind: []DepBehind{}}
109	for _, r := range reports {
110		out.Behind = append(out.Behind, DepBehind{r.Ecosystem, r.Name, r.Current, r.Latest})
111	}
112	return c.emit(out, func(w io.Writer) {
113		tracked := ""
114		if check.IssueNumber != 0 {
115			tracked = fmt.Sprintf("#%d", check.IssueNumber)
116		}
117		v := c.view(w)
118		v.fields(
119			"checks", "on",
120			"last check", c.when(check.LastCheck),
121			"last error", check.LastError,
122			"tracked in", tracked,
123		)
124		if len(out.Behind) > 0 {
125			v.section("behind")
126			tb := c.table(w, "ECOSYSTEM", "NAME", "CURRENT", "LATEST")
127			for _, b := range out.Behind {
128				tb.row(cText(b.Ecosystem), cRef(b.Name), cText(b.Current), cText(b.Latest))
129			}
130			tb.flush()
131		}
132	})
133}