github.com/abayer/test-infra@v0.0.5/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/hook"
    33  	"k8s.io/test-infra/prow/kube"
    34  	"k8s.io/test-infra/prow/pjutil"
    35  	"k8s.io/test-infra/prow/pluginhelp"
    36  	"k8s.io/test-infra/prow/report"
    37  )
    38  
    39  const pluginName = "refresh"
    40  
    41  var refreshRe = regexp.MustCompile(`(?mi)^/refresh\s*$`)
    42  
    43  func helpProvider(enabledRepos []string) (*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 := hook.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 := ioutil.ReadAll(resp.Body)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	var list struct {
   137  		PJs []kube.ProwJob `json:"items"`
   138  	}
   139  	if err := json.Unmarshal(data, &list); err != nil {
   140  		return fmt.Errorf("cannot unmarshal data from deck: %v", err)
   141  	}
   142  
   143  	pr, err := s.ghc.GetPullRequest(org, repo, num)
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	var presubmits []kube.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.ReportTemplate
   172  	for _, pj := range pjutil.GetLatestProwJobs(presubmits, kube.PresubmitJob) {
   173  		var reportTemplate *template.Template
   174  		switch pj.Spec.Agent {
   175  		case kube.KubernetesAgent:
   176  			reportTemplate = kubeReport
   177  		case kube.JenkinsAgent:
   178  			reportTemplate = s.reportForProwJob(pj, jenkinsConfig)
   179  		}
   180  		if reportTemplate == nil {
   181  			continue
   182  		}
   183  
   184  		s.log.WithFields(l.Data).Infof("Refreshing the status of job %q (pj: %s)", pj.Spec.Job, pj.ObjectMeta.Name)
   185  		if err := report.Report(s.ghc, reportTemplate, pj); err != nil {
   186  			s.log.WithError(err).WithFields(l.Data).Info("Failed report.")
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  func (s *server) reportForProwJob(pj kube.ProwJob, configs []config.JenkinsOperator) *template.Template {
   193  	for _, cfg := range configs {
   194  		if cfg.LabelSelector.Matches(labels.Set(pj.Labels)) {
   195  			return cfg.ReportTemplate
   196  		}
   197  	}
   198  	return nil
   199  }