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  }