Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 25m ago 32290114
history blame raw 3.9 KiB
1package web
2
3import (
4 "net/http"
5 "sort"
6 "strings"
7
8 "rodent.software/vole/ci"
9)
10
11type dispatchInputView struct {
12 Name string
13 Description string
14 Type string
15 Required bool
16 Default string
17 Options []string
18}
19
20type dispatchView struct {
21 Page
22 Name string
23 Workflow *ci.Workflow
24 Ref string
25 Branches []string
26 Inputs []dispatchInputView
27 Error string
28}
29
30// WorkflowDispatchPage shows the form for one workflow that declared
31// workflow-dispatch. It is a maintainer surface: the run executes repository code.
32func (w *Web) WorkflowDispatchPage(rw http.ResponseWriter, r *http.Request) {
33 m, ok := w.open(rw, r)
34 if !ok {
35 return
36 }
37 if !w.canDispatch(rw, r, m.Name) {
38 return
39 }
40 w.renderDispatch(rw, r, m.Name, r.URL.Query().Get("workflow"), r.URL.Query().Get("ref"), "")
41}
42
43// DispatchWorkflow queues a manual run from the browser form.
44func (w *Web) WorkflowDispatch(rw http.ResponseWriter, r *http.Request) {
45 ctx, ok := w.beginWrite(rw, r, maxFormBody)
46 if !ok {
47 return
48 }
49 m := ctx.Repo
50 if !w.canDispatch(rw, r, m.Name) {
51 return
52 }
53 workflow := strings.TrimSpace(r.PostFormValue("workflow"))
54 ref := strings.TrimSpace(r.PostFormValue("ref"))
55 inputs := map[string]string{}
56 for name, values := range r.PostForm {
57 switch name {
58 case "csrf", "workflow", "ref":
59 continue
60 }
61 if len(values) > 0 {
62 inputs[name] = values[0]
63 }
64 }
65 if w.DispatchWorkflow == nil {
66 w.serverError(rw, r, "manual runs are not configured on this server")
67 return
68 }
69 if _, err := w.DispatchWorkflow(m.Name, workflow, ref, inputs, ctx.Name); err != nil {
70 w.renderDispatch(rw, r, m.Name, workflow, ref, err.Error())
71 return
72 }
73 w.audit("web.workflow.dispatch", ctx.Name, m.Name, workflow+" "+ref)
74 http.Redirect(rw, r, "/"+m.Name+"/runs", http.StatusSeeOther)
75}
76
77func (w *Web) canDispatch(rw http.ResponseWriter, r *http.Request, repoName string) bool {
78 m, err := w.Repos.Open(repoName)
79 if err != nil {
80 w.notFound(rw, r)
81 return false
82 }
83 pr := w.principal(r)
84 if pr == nil {
85 w.notFound(rw, r)
86 return false
87 }
88 acl, err := m.ACL()
89 if err != nil || acl == nil {
90 w.notFound(rw, r)
91 return false
92 }
93 if !canMaintain(acl, pr.account.Name) &&
94 !(w.Ident != nil && w.Ident.IsAdmin(pr.account.Name)) {
95 w.notFound(rw, r)
96 return false
97 }
98 return true
99}
100
101func (w *Web) renderDispatch(rw http.ResponseWriter, r *http.Request, repoName, workflowPath, ref, message string) {
102 m, err := w.Repos.Open(repoName)
103 if err != nil {
104 w.notFound(rw, r)
105 return
106 }
107 commit, _ := m.Git().RevParse(m.DefaultBranch)
108 wfs, problems := ci.At(m.Git(), commit)
109 if len(problems) > 0 {
110 w.render(rw, "workflow_dispatch", dispatchView{
111 Page: w.page(r, m.Name+" · run workflow"), Name: m.Name, Error: strings.Join(problems, "; "),
112 }, 0)
113 return
114 }
115 var wf *ci.Workflow
116 for _, candidate := range wfs {
117 if candidate.Path == workflowPath {
118 wf = candidate
119 break
120 }
121 }
122 if wf == nil {
123 w.notFound(rw, r)
124 return
125 }
126 if wf.Dispatch == nil {
127 http.Error(rw, "this workflow does not declare workflow-dispatch", http.StatusBadRequest)
128 return
129 }
130 if strings.TrimSpace(ref) == "" {
131 ref = m.DefaultBranch
132 }
133 refs, _ := m.Git().ListRefs("refs/heads/")
134 branches := make([]string, 0, len(refs))
135 for name := range refs {
136 branches = append(branches, strings.TrimPrefix(name, "refs/heads/"))
137 }
138 sort.Strings(branches)
139 names := make([]string, 0, len(wf.Dispatch.Inputs))
140 for name := range wf.Dispatch.Inputs {
141 names = append(names, name)
142 }
143 sort.Strings(names)
144 inputs := make([]dispatchInputView, 0, len(names))
145 for _, name := range names {
146 in := wf.Dispatch.Inputs[name]
147 inputs = append(inputs, dispatchInputView{
148 Name: name, Description: in.Description, Type: in.Type,
149 Required: in.Required, Default: in.Default, Options: in.Options,
150 })
151 }
152 w.render(rw, "workflow_dispatch", dispatchView{
153 Page: w.page(r, m.Name+" · run workflow"), Name: m.Name, Workflow: wf,
154 Ref: ref, Branches: branches, Inputs: inputs, Error: message,
155 }, 0)
156}
157