github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/external-plugins/refresh/server.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "net/http" 25 "regexp" 26 "text/template" 27 28 "github.com/sirupsen/logrus" 29 "k8s.io/apimachinery/pkg/labels" 30 31 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 32 "sigs.k8s.io/prow/pkg/config" 33 "sigs.k8s.io/prow/pkg/github" 34 "sigs.k8s.io/prow/pkg/github/report" 35 "sigs.k8s.io/prow/pkg/pjutil" 36 "sigs.k8s.io/prow/pkg/pluginhelp" 37 ) 38 39 const pluginName = "refresh" 40 41 var refreshRe = regexp.MustCompile(`(?mi)^/refresh\s*$`) 42 43 func helpProvider(_ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 44 pluginHelp := &pluginhelp.PluginHelp{ 45 Description: `The refresh plugin is used for refreshing status contexts in PRs. Useful in case GitHub breaks down.`, 46 } 47 pluginHelp.AddCommand(pluginhelp.Command{ 48 Usage: "/refresh", 49 Description: "Refresh status contexts on a PR.", 50 WhoCanUse: "Anyone", 51 Examples: []string{"/refresh"}, 52 }) 53 return pluginHelp, nil 54 } 55 56 type server struct { 57 tokenGenerator func() []byte 58 prowURL string 59 configAgent *config.Agent 60 ghc github.Client 61 log *logrus.Entry 62 } 63 64 func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 65 eventType, eventGUID, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator) 66 if !ok { 67 return 68 } 69 fmt.Fprint(w, "Event received. Have a nice day.") 70 71 if err := s.handleEvent(eventType, eventGUID, payload); err != nil { 72 logrus.WithError(err).Error("Error parsing event.") 73 } 74 } 75 76 func (s *server) handleEvent(eventType, eventGUID string, payload []byte) error { 77 l := logrus.WithFields( 78 logrus.Fields{ 79 "event-type": eventType, 80 github.EventGUID: eventGUID, 81 }, 82 ) 83 84 switch eventType { 85 case "issue_comment": 86 var ic github.IssueCommentEvent 87 if err := json.Unmarshal(payload, &ic); err != nil { 88 return err 89 } 90 go func() { 91 if err := s.handleIssueComment(l, ic); err != nil { 92 s.log.WithError(err).WithFields(l.Data).Info("Refreshing github statuses failed.") 93 } 94 }() 95 default: 96 logrus.Debugf("skipping event of type %q", eventType) 97 } 98 return nil 99 } 100 101 func (s *server) handleIssueComment(l *logrus.Entry, ic github.IssueCommentEvent) error { 102 if !ic.Issue.IsPullRequest() || ic.Action != github.IssueCommentActionCreated || ic.Issue.State == "closed" { 103 return nil 104 } 105 106 org := ic.Repo.Owner.Login 107 repo := ic.Repo.Name 108 num := ic.Issue.Number 109 110 l = l.WithFields(logrus.Fields{ 111 github.OrgLogField: org, 112 github.RepoLogField: repo, 113 github.PrLogField: num, 114 }) 115 116 if !refreshRe.MatchString(ic.Comment.Body) { 117 return nil 118 } 119 s.log.WithFields(l.Data).Info("Requested a status refresh.") 120 121 // TODO: Retries 122 resp, err := http.Get(s.prowURL + "/prowjobs.js") 123 if err != nil { 124 return err 125 } 126 defer resp.Body.Close() 127 if resp.StatusCode < 200 || resp.StatusCode > 299 { 128 return fmt.Errorf("status code not 2XX: %v", resp.Status) 129 } 130 131 data, err := io.ReadAll(resp.Body) 132 if err != nil { 133 return err 134 } 135 136 var list struct { 137 PJs []prowapi.ProwJob `json:"items"` 138 } 139 if err := json.Unmarshal(data, &list); err != nil { 140 return fmt.Errorf("cannot unmarshal data from deck: %w", err) 141 } 142 143 pr, err := s.ghc.GetPullRequest(org, repo, num) 144 if err != nil { 145 return err 146 } 147 148 var presubmits []prowapi.ProwJob 149 for _, pj := range list.PJs { 150 if pj.Spec.Type != "presubmit" { 151 continue 152 } 153 if !pj.Spec.Report { 154 continue 155 } 156 if pj.Spec.Refs.Pulls[0].Number != num { 157 continue 158 } 159 if pj.Spec.Refs.Pulls[0].SHA != pr.Head.SHA { 160 continue 161 } 162 presubmits = append(presubmits, pj) 163 } 164 165 if len(presubmits) == 0 { 166 s.log.WithFields(l.Data).Info("No prowjobs found.") 167 return nil 168 } 169 170 jenkinsConfig := s.configAgent.Config().JenkinsOperators 171 kubeReport := s.configAgent.Config().Plank.ReportTemplateForRepo(&prowapi.Refs{Org: org, Repo: repo}) 172 reportConfig := s.configAgent.Config().GitHubReporter 173 for _, pj := range pjutil.GetLatestProwJobs(presubmits, prowapi.PresubmitJob) { 174 var reportTemplate *template.Template 175 switch pj.Spec.Agent { 176 case prowapi.KubernetesAgent: 177 reportTemplate = kubeReport 178 case prowapi.JenkinsAgent: 179 reportTemplate = s.reportForProwJob(pj, jenkinsConfig) 180 } 181 if reportTemplate == nil { 182 continue 183 } 184 185 s.log.WithFields(l.Data).Infof("Refreshing the status of job %q (pj: %s)", pj.Spec.Job, pj.ObjectMeta.Name) 186 if err := report.Report(context.Background(), s.ghc, reportTemplate, pj, reportConfig); err != nil { 187 s.log.WithError(err).WithFields(l.Data).Info("Failed report.") 188 } 189 } 190 return nil 191 } 192 193 func (s *server) reportForProwJob(pj prowapi.ProwJob, configs []config.JenkinsOperator) *template.Template { 194 for _, cfg := range configs { 195 if cfg.LabelSelector.Matches(labels.Set(pj.Labels)) { 196 return cfg.ReportTemplateForRepo(pj.Spec.Refs) 197 } 198 } 199 return nil 200 }