This repository has no description
1package pulls
2
3import (
4 "fmt"
5 "net/http"
6 "strings"
7
8 "tangled.org/core/api/tangled"
9 "tangled.org/core/appview/db"
10 "tangled.org/core/appview/models"
11)
12
13// TriggerCi manually triggers a CI pipeline for a fork-based pull request.
14// authorized against and recorded under the target repo, but checked out
15// from the fork at the latest round's commit.
16func (s *Pulls) TriggerCi(w http.ResponseWriter, r *http.Request) {
17 l := s.logger.With("handler", "TriggerCi")
18 errorId := "pull-error"
19
20 fail := func(msg string, err error) {
21 if err != nil {
22 l.Error(msg, "err", err)
23 } else {
24 l.Error(msg)
25 }
26 s.pages.Notice(w, errorId, msg)
27 }
28
29 f, err := s.repoResolver.Resolve(r)
30 if err != nil {
31 fail("failed to resolve repository", err)
32 return
33 }
34
35 pull, ok := r.Context().Value("pull").(*models.Pull)
36 if !ok {
37 fail("failed to get pull", nil)
38 return
39 }
40 l = l.With("pull_id", pull.PullId)
41
42 if !pull.IsForkBased() {
43 fail("this pull request is not fork-based", nil)
44 return
45 }
46
47 if f.Spindle == "" {
48 fail("this repository has no spindle configured", nil)
49 return
50 }
51
52 latest := pull.LatestSubmission()
53 if latest.SourceRev == "" {
54 fail("cannot trigger ci: this round has no commit to run", nil)
55 return
56 }
57
58 workflowsChanged, changedFiles, err := s.workflowChangeFromSpindle(r, f, pull)
59 if err != nil {
60 // without the fingerprint comparison there is no way to tell fork
61 // workflows apart from reviewed ones, so refuse to run them
62 fail("failed to verify the workflow definitions of this round", err)
63 return
64 }
65 if workflowsChanged && r.URL.Query().Get("confirm") != "1" {
66 fail(fmt.Sprintf("workflow files changed in this round (%s); review before running", strings.Join(changedFiles, ", ")), nil)
67 return
68 }
69
70 forkRepo, err := db.GetRepoByDid(s.db, pull.PullSource.RepoDid.String())
71 if err != nil {
72 fail("failed to resolve the fork this pull request comes from", err)
73 return
74 }
75
76 spindleClient, err := s.oauth.SpindleServiceClient(r, f.Spindle, tangled.CiTriggerPipelineNSID)
77 if err != nil {
78 fail("failed to authorize with spindle", err)
79 return
80 }
81
82 pullAt := pull.AtUri().String()
83 sourceBranch := pull.PullSource.Branch
84 targetBranch := pull.TargetBranch
85 out, err := tangled.CiTriggerPipeline(
86 r.Context(),
87 spindleClient,
88 &tangled.CiTriggerPipeline_Input{
89 Repo: f.RepoDid,
90 Trigger: &tangled.CiTriggerPipeline_Input_Trigger{
91 CiTrigger_PullRequest: &tangled.CiTrigger_PullRequest{
92 Pull: &pullAt,
93 SourceBranch: &sourceBranch,
94 SourceRepo: &forkRepo.RepoDid,
95 SourceSha: latest.SourceRev,
96 TargetBranch: targetBranch,
97 },
98 },
99 },
100 )
101 if err != nil {
102 fail("spindle rejected the trigger", err)
103 return
104 }
105 l.Info("triggered ci for fork-based pull", "pipeline", out.Pipeline)
106
107 user := s.oauth.GetMultiAccountUser(r)
108 repoInfo := s.repoResolver.GetRepoInfo(r, user)
109 dest := fmt.Sprintf("/%s/pulls/%d/round/%d", repoInfo.FullName(), pull.PullId, pull.LastRoundNumber())
110 if r.Header.Get("HX-Request") == "true" {
111 w.Header().Set("HX-Redirect", dest)
112 w.WriteHeader(http.StatusOK)
113 return
114 }
115 http.Redirect(w, r, dest, http.StatusSeeOther)
116}