internal/control/deps.go
117 lines · 3744 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>", Run: runDepsEnable})
20 register(Command{Path: []string{"repo", "deps", "disable"},
21 Summary: "stop checking dependencies",
22 Usage: "repo deps disable <owner/name>", Run: runDepsDisable})
23 register(Command{Path: []string{"repo", "deps", "status"},
24 Summary: "show dependency check state",
25 Usage: "repo deps status <owner/name>", ReadOnly: true, Run: runDepsStatus})
26}
27
28// DepsOut is what `repo deps status` emits: the check's state and what
29// the last run found behind. Named so the web decodes the same struct
30// the command writes.
31type DepsOut struct {
32 Enabled bool `json:"enabled"`
33 LastCheck string `json:"last_check,omitempty"`
34 LastError string `json:"last_error,omitempty"`
35 IssueNumber int64 `json:"issue_number,omitempty"`
36 Behind []DepBehind `json:"behind"`
37}
38
39// DepBehind is one dependency with a newer release than the manifest pins.
40type DepBehind struct {
41 Ecosystem string `json:"ecosystem"`
42 Name string `json:"name"`
43 Current string `json:"current"`
44 Latest string `json:"latest"`
45}
46
47func runDepsEnable(c *Ctx, args []string) int {
48 if len(args) != 1 {
49 return c.usage()
50 }
51 repo, code := resolveRepo(c, args[0], policy.CanAdmin)
52 if code >= 0 {
53 return code
54 }
55 if err := c.Store.EnableDepCheck(repo.ID); err != nil {
56 return c.fail(protocol.ExitFailure, "%v", err)
57 }
58 return c.emit(map[string]any{"enabled": true}, func(w io.Writer) {
59 fmt.Fprintf(w, "dependency checks enabled on %s\n", repo.Path())
60 })
61}
62
63func runDepsDisable(c *Ctx, args []string) int {
64 if len(args) != 1 {
65 return c.usage()
66 }
67 repo, code := resolveRepo(c, args[0], policy.CanAdmin)
68 if code >= 0 {
69 return code
70 }
71 if err := c.Store.DisableDepCheck(repo.ID); err != nil {
72 return c.fail(protocol.ExitFailure, "%v", err)
73 }
74 return c.emit(map[string]any{"enabled": false}, func(w io.Writer) {
75 fmt.Fprintf(w, "dependency checks disabled on %s\n", repo.Path())
76 })
77}
78
79func runDepsStatus(c *Ctx, args []string) int {
80 if len(args) != 1 {
81 return c.usage()
82 }
83 repo, code := resolveRepo(c, args[0], policy.CanRead)
84 if code >= 0 {
85 return code
86 }
87 check, err := c.Store.DepCheckFor(repo.ID)
88 if errors.Is(err, store.ErrNotFound) {
89 return c.emit(map[string]any{"enabled": false}, func(w io.Writer) {
90 fmt.Fprintf(w, "dependency checks are off for %s (repo deps enable %s)\n", repo.Path(), repo.Path())
91 })
92 }
93 if err != nil {
94 return c.fail(protocol.ExitFailure, "%v", err)
95 }
96 reports, err := c.Store.ReportedDeps(repo.ID)
97 if err != nil {
98 return c.fail(protocol.ExitFailure, "%v", err)
99 }
100 out := DepsOut{Enabled: true, LastCheck: check.LastCheck, LastError: check.LastError,
101 IssueNumber: check.IssueNumber, Behind: []DepBehind{}}
102 for _, r := range reports {
103 out.Behind = append(out.Behind, DepBehind{r.Ecosystem, r.Name, r.Current, r.Latest})
104 }
105 return c.emit(out, func(w io.Writer) {
106 fmt.Fprintf(w, "checks on, last %s\n", orDash(check.LastCheck))
107 if check.LastError != "" {
108 fmt.Fprintf(w, "last error: %s\n", check.LastError)
109 }
110 if check.IssueNumber != 0 {
111 fmt.Fprintf(w, "tracked in #%d\n", check.IssueNumber)
112 }
113 for _, b := range out.Behind {
114 fmt.Fprintf(w, "%s\t%s\t%s\t-> %s\n", b.Ecosystem, b.Name, b.Current, b.Latest)
115 }
116 })
117}