package control import ( "errors" "fmt" "io" "gitbay.org/gitbay/internal/policy" "gitbay.org/gitbay/internal/protocol" "gitbay.org/gitbay/internal/store" ) func init() { // Dependency update checks. Opt-in per repository: the check tells a // public registry what the repository depends on, which is the owner's // disclosure to make, not the instance's. register(Command{Path: []string{"repo", "deps", "enable"}, Summary: "check dependencies for updates", Usage: "repo deps enable ", Examples: []string{"repo deps enable krz/gitbay"}, Run: runDepsEnable}) register(Command{Path: []string{"repo", "deps", "disable"}, Summary: "stop checking dependencies", Usage: "repo deps disable ", Examples: []string{"repo deps disable krz/gitbay"}, Run: runDepsDisable}) register(Command{Path: []string{"repo", "deps", "status"}, Summary: "show dependency check state", Usage: "repo deps status ", Examples: []string{"repo deps status krz/gitbay"}, ReadOnly: true, Run: runDepsStatus}) } // DepsOut is what `repo deps status` emits: the check's state and what // the last run found behind. Named so the web decodes the same struct // the command writes. type DepsOut struct { Enabled bool `json:"enabled"` LastCheck string `json:"last_check,omitempty"` LastError string `json:"last_error,omitempty"` IssueNumber int64 `json:"issue_number,omitempty"` Behind []DepBehind `json:"behind"` } // DepBehind is one dependency with a newer release than the manifest pins. type DepBehind struct { Ecosystem string `json:"ecosystem"` Name string `json:"name"` Current string `json:"current"` Latest string `json:"latest"` } func runDepsEnable(c *Ctx, args []string) int { if len(args) != 1 { return c.usage() } repo, code := resolveRepo(c, args[0], policy.CanAdmin) if code >= 0 { return code } if err := c.Store.EnableDepCheck(repo.ID); err != nil { return c.fail(protocol.ExitFailure, "%v", err) } return c.emit(map[string]any{"enabled": true}, func(w io.Writer) { fmt.Fprintf(w, "dependency checks enabled on %s\n", repo.Path()) }) } func runDepsDisable(c *Ctx, args []string) int { if len(args) != 1 { return c.usage() } repo, code := resolveRepo(c, args[0], policy.CanAdmin) if code >= 0 { return code } if err := c.Store.DisableDepCheck(repo.ID); err != nil { return c.fail(protocol.ExitFailure, "%v", err) } return c.emit(map[string]any{"enabled": false}, func(w io.Writer) { fmt.Fprintf(w, "dependency checks disabled on %s\n", repo.Path()) }) } func runDepsStatus(c *Ctx, args []string) int { if len(args) != 1 { return c.usage() } repo, code := resolveRepo(c, args[0], policy.CanRead) if code >= 0 { return code } check, err := c.Store.DepCheckFor(repo.ID) if errors.Is(err, store.ErrNotFound) { return c.emit(map[string]any{"enabled": false}, func(w io.Writer) { v := c.view(w) v.fields("checks", fmt.Sprintf("off (repo deps enable %s)", repo.Path())) }) } if err != nil { return c.fail(protocol.ExitFailure, "%v", err) } reports, err := c.Store.ReportedDeps(repo.ID) if err != nil { return c.fail(protocol.ExitFailure, "%v", err) } out := DepsOut{Enabled: true, LastCheck: check.LastCheck, LastError: check.LastError, IssueNumber: check.IssueNumber, Behind: []DepBehind{}} for _, r := range reports { out.Behind = append(out.Behind, DepBehind{r.Ecosystem, r.Name, r.Current, r.Latest}) } return c.emit(out, func(w io.Writer) { tracked := "" if check.IssueNumber != 0 { tracked = fmt.Sprintf("#%d", check.IssueNumber) } v := c.view(w) v.fields( "checks", "on", "last check", c.when(check.LastCheck), "last error", check.LastError, "tracked in", tracked, ) if len(out.Behind) > 0 { io.WriteString(w, "\n") tb := c.table(w, "ECOSYSTEM", "NAME", "CURRENT", "LATEST") for _, b := range out.Behind { tb.row(cText(b.Ecosystem), cRef(b.Name), cText(b.Current), cText(b.Latest)) } tb.flush() } }) }