github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/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 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "net/http" 24 "regexp" 25 "text/template" 26 27 "github.com/sirupsen/logrus" 28 "k8s.io/apimachinery/pkg/labels" 29 30 "k8s.io/test-infra/prow/config" 31 "k8s.io/test-infra/prow/github" 32 "k8s.io/test-infra/prow/kube" 33 "k8s.io/test-infra/prow/pjutil" 34 "k8s.io/test-infra/prow/pluginhelp" 35 "k8s.io/test-infra/prow/report" 36 ) 37 38 const pluginName = "refresh" 39 40 var refreshRe = regexp.MustCompile(`(?mi)^/refresh\s*$`) 41 42 func helpProvider(enabledRepos []string) (*pluginhelp.PluginHelp, error) { 43 pluginHelp := &pluginhelp.PluginHelp{ 44 Description: `The refresh plugin is used for refreshing status contexts in PRs. Useful in case Github breaks down.`, 45 } 46 pluginHelp.AddCommand(pluginhelp.Command{ 47 Usage: "/refresh", 48 Description: "Refresh status contexts on a PR.", 49 WhoCanUse: "Anyone", 50 Examples: []string{"/refresh"}, 51 }) 52 return pluginHelp, nil 53 } 54 55 type server struct { 56 tokenGenerator func() []byte 57 prowURL string 58 configAgent *config.Agent 59 ghc *github.Client 60 log *logrus.Entry 61 } 62 63 func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 64 eventType, eventGUID, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator()) 65 if !ok { 66 return 67 } 68 fmt.Fprint(w, "Event received. Have a nice day.") 69 70 if err := s.handleEvent(eventType, eventGUID, payload); err != nil { 71 logrus.WithError(err).Error("Error parsing event.") 72 } 73 } 74 75 func (s *server) handleEvent(eventType, eventGUID string, payload []byte) error { 76 l := logrus.WithFields( 77 logrus.Fields{ 78 "event-type": eventType, 79 github.EventGUID: eventGUID, 80 }, 81 ) 82 83 switch eventType { 84 case "issue_comment": 85 var ic github.IssueCommentEvent 86 if err := json.Unmarshal(payload, &ic); err != nil { 87 return err 88 } 89 go func() { 90 if err := s.handleIssueComment(l, ic); err != nil { 91 s.log.WithError(err).WithFields(l.Data).Info("Refreshing github statuses failed.") 92 } 93 }() 94 default: 95 logrus.Debugf("skipping event of type %q", eventType) 96 } 97 return nil 98 } 99 100 func (s *server) handleIssueComment(l *logrus.Entry, ic github.IssueCommentEvent) error { 101 if !ic.Issue.IsPullRequest() || ic.Action != github.IssueCommentActionCreated || ic.Issue.State == "closed" { 102 return nil 103 } 104 105 org := ic.Repo.Owner.Login 106 repo := ic.Repo.Name 107 num := ic.Issue.Number 108 109 l = l.WithFields(logrus.Fields{ 110 github.OrgLogField: org, 111 github.RepoLogField: repo, 112 github.PrLogField: num, 113 }) 114 115 if !refreshRe.MatchString(ic.Comment.Body) { 116 return nil 117 } 118 s.log.WithFields(l.Data).Info("Requested a status refresh.") 119 120 // TODO: Retries 121 resp, err := http.Get(s.prowURL + "/prowjobs.js") 122 if err != nil { 123 return err 124 } 125 defer resp.Body.Close() 126 if resp.StatusCode < 200 || resp.StatusCode > 299 { 127 return fmt.Errorf("status code not 2XX: %v", resp.Status) 128 } 129 130 data, err := ioutil.ReadAll(resp.Body) 131 if err != nil { 132 return err 133 } 134 135 var list struct { 136 PJs []kube.ProwJob `json:"items"` 137 } 138 if err := json.Unmarshal(data, &list); err != nil { 139 return fmt.Errorf("cannot unmarshal data from deck: %v", err) 140 } 141 142 pr, err := s.ghc.GetPullRequest(org, repo, num) 143 if err != nil { 144 return err 145 } 146 147 var presubmits []kube.ProwJob 148 for _, pj := range list.PJs { 149 if pj.Spec.Type != "presubmit" { 150 continue 151 } 152 if !pj.Spec.Report { 153 continue 154 } 155 if pj.Spec.Refs.Pulls[0].Number != num { 156 continue 157 } 158 if pj.Spec.Refs.Pulls[0].SHA != pr.Head.SHA { 159 continue 160 } 161 presubmits = append(presubmits, pj) 162 } 163 164 if len(presubmits) == 0 { 165 s.log.WithFields(l.Data).Info("No prowjobs found.") 166 return nil 167 } 168 169 jenkinsConfig := s.configAgent.Config().JenkinsOperators 170 kubeReport := s.configAgent.Config().Plank.ReportTemplate 171 for _, pj := range pjutil.GetLatestProwJobs(presubmits, kube.PresubmitJob) { 172 var reportTemplate *template.Template 173 switch pj.Spec.Agent { 174 case kube.KubernetesAgent: 175 reportTemplate = kubeReport 176 case kube.JenkinsAgent: 177 reportTemplate = s.reportForProwJob(pj, jenkinsConfig) 178 } 179 if reportTemplate == nil { 180 continue 181 } 182 183 s.log.WithFields(l.Data).Infof("Refreshing the status of job %q (pj: %s)", pj.Spec.Job, pj.ObjectMeta.Name) 184 if err := report.Report(s.ghc, reportTemplate, pj); err != nil { 185 s.log.WithError(err).WithFields(l.Data).Info("Failed report.") 186 } 187 } 188 return nil 189 } 190 191 func (s *server) reportForProwJob(pj kube.ProwJob, configs []config.JenkinsOperator) *template.Template { 192 for _, cfg := range configs { 193 if cfg.LabelSelector.Matches(labels.Set(pj.Labels)) { 194 return cfg.ReportTemplate 195 } 196 } 197 return nil 198 }