github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/pony/pony.go (about)

     1  /*
     2  Copyright 2018 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 pony adds pony images to the issue or PR in response to a /pony comment
    18  package pony
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"net/url"
    26  	"regexp"
    27  
    28  	"github.com/sirupsen/logrus"
    29  	"k8s.io/test-infra/prow/github"
    30  	"k8s.io/test-infra/prow/pluginhelp"
    31  	"k8s.io/test-infra/prow/plugins"
    32  )
    33  
    34  // Only the properties we actually use.
    35  type ponyResult struct {
    36  	Pony ponyResultPony `json:"pony"`
    37  }
    38  
    39  type ponyResultPony struct {
    40  	Representations ponyRepresentations `json:"representations"`
    41  }
    42  
    43  type ponyRepresentations struct {
    44  	Full  string `json:"full"`
    45  	Small string `json:"small"`
    46  }
    47  
    48  const (
    49  	ponyURL    = realHerd("https://theponyapi.com/pony.json")
    50  	pluginName = "pony"
    51  )
    52  
    53  var (
    54  	match = regexp.MustCompile(`(?mi)^/(?:pony)(?:\s+(.+?))?\s*$`)
    55  )
    56  
    57  func init() {
    58  	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
    59  }
    60  
    61  func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
    62  	// The Config field is omitted because this plugin is not configurable.
    63  	pluginHelp := &pluginhelp.PluginHelp{
    64  		Description: "The pony plugin adds a pony image to an issue or PR in response to the `/pony` command.",
    65  	}
    66  	pluginHelp.AddCommand(pluginhelp.Command{
    67  		Usage:       "/(pony) [pony]",
    68  		Description: "Add a little pony image to the issue or PR. A particular pony can optionally be named for a picture of that specific pony.",
    69  		Featured:    false,
    70  		WhoCanUse:   "Anyone",
    71  		Examples:    []string{"/pony", "/pony Twilight Sparkle"},
    72  	})
    73  	return pluginHelp, nil
    74  }
    75  
    76  var client = http.Client{}
    77  
    78  type githubClient interface {
    79  	CreateComment(owner, repo string, number int, comment string) error
    80  }
    81  
    82  type herd interface {
    83  	readPony(string) (string, error)
    84  }
    85  
    86  type realHerd string
    87  
    88  func formatURLs(small, full string) string {
    89  	return fmt.Sprintf("[![pony image](%s)](%s)", small, full)
    90  }
    91  
    92  func (h realHerd) readPony(tags string) (string, error) {
    93  	// Omit webm video (the only video type) and anything too far off square.
    94  	q := "-webm, aspect_ratio:1~0.5"
    95  	if tags != "" {
    96  		q += ", " + tags
    97  	}
    98  	uri := string(h) + "?q=" + url.QueryEscape(q)
    99  	resp, err := client.Get(uri)
   100  	if err != nil {
   101  		return "", fmt.Errorf("failed to make request: %v", err)
   102  	}
   103  	defer resp.Body.Close()
   104  	if resp.StatusCode != http.StatusOK {
   105  		return "", fmt.Errorf("no pony found")
   106  	}
   107  	var a ponyResult
   108  	if err = json.NewDecoder(resp.Body).Decode(&a); err != nil {
   109  		return "", fmt.Errorf("failed to decode response: %v", err)
   110  	}
   111  
   112  	embedded := a.Pony.Representations.Small
   113  	tooBig, err := github.ImageTooBig(embedded)
   114  	if err != nil {
   115  		return "", fmt.Errorf("couldn't fetch pony for size check: %v", err)
   116  	}
   117  	if tooBig {
   118  		return "", fmt.Errorf("the pony is too big")
   119  	}
   120  	return formatURLs(a.Pony.Representations.Small, a.Pony.Representations.Full), nil
   121  }
   122  
   123  func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
   124  	return handle(pc.GitHubClient, pc.Logger, &e, ponyURL)
   125  }
   126  
   127  func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, p herd) error {
   128  	// Only consider new comments.
   129  	if e.Action != github.GenericCommentActionCreated {
   130  		return nil
   131  	}
   132  	// Make sure they are requesting a pony
   133  	mat := match.FindStringSubmatch(e.Body)
   134  	if mat == nil {
   135  		return nil
   136  	}
   137  
   138  	tag := mat[1]
   139  	org := e.Repo.Owner.Login
   140  	repo := e.Repo.Name
   141  	number := e.Number
   142  
   143  	for i := 0; i < 5; i++ {
   144  		resp, err := p.readPony(tag)
   145  		if err != nil {
   146  			log.WithError(err).Println("Failed to get a pony")
   147  			continue
   148  		}
   149  		return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp))
   150  	}
   151  
   152  	var msg string
   153  	if tag != "" {
   154  		msg = "Couldn't find a pony matching that query."
   155  	} else {
   156  		msg = "https://theponyapi.com appears to be down"
   157  	}
   158  	if err := gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
   159  		log.WithError(err).Error("Failed to leave comment")
   160  	}
   161  
   162  	return errors.New("could not find a valid pony image")
   163  }