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("[](%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 }