sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/dog/dog.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 dog adds dog images to the issue or PR in response to a /woof comment 18 package dog 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 30 "sigs.k8s.io/prow/pkg/config" 31 "sigs.k8s.io/prow/pkg/github" 32 "sigs.k8s.io/prow/pkg/pluginhelp" 33 "sigs.k8s.io/prow/pkg/plugins" 34 ) 35 36 var ( 37 match = regexp.MustCompile(`(?mi)^/(woof|bark)\s*$`) 38 fineRegex = regexp.MustCompile(`(?mi)^/this-is-fine\s*$`) 39 notFineRegex = regexp.MustCompile(`(?mi)^/this-is-not-fine\s*$`) 40 unbearableRegex = regexp.MustCompile(`(?mi)^/this-is-unbearable\s*$`) 41 filetypes = regexp.MustCompile(`(?i)\.(jpg|gif|png)$`) 42 ) 43 44 const ( 45 dogURL = realPack("https://random.dog/woof.json") 46 defaultFineImagesRoot = "https://storage.googleapis.com/this-is-fine-images/" 47 fineIMG = "this_is_fine.png" 48 notFineIMG = "this_is_not_fine.png" 49 unbearableIMG = "this_is_unbearable.jpg" 50 pluginName = "dog" 51 ) 52 53 func init() { 54 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) 55 } 56 57 func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 58 // The Config field is omitted because this plugin is not configurable. 59 pluginHelp := &pluginhelp.PluginHelp{ 60 Description: "The dog plugin adds a dog image to an issue or PR in response to the `/woof` command.", 61 } 62 pluginHelp.AddCommand(pluginhelp.Command{ 63 Usage: "/(woof|bark|this-is-{fine|not-fine|unbearable})", 64 Description: "Add a dog image to the issue or PR", 65 Featured: false, 66 WhoCanUse: "Anyone", 67 Examples: []string{"/woof", "/bark", "/this-is-{fine|not-fine|unbearable}"}, 68 }) 69 return pluginHelp, nil 70 } 71 72 type githubClient interface { 73 CreateComment(owner, repo string, number int, comment string) error 74 } 75 76 type pack interface { 77 readDog(dogURL string) (string, error) 78 } 79 80 type realPack string 81 82 var client = http.Client{} 83 84 type dogResult struct { 85 URL string `json:"url"` 86 } 87 88 // FormatURL will return the GH markdown to show the image for a specific dogURL. 89 func FormatURL(dogURL string) (string, error) { 90 if dogURL == "" { 91 return "", errors.New("empty url") 92 } 93 src, err := url.ParseRequestURI(dogURL) 94 if err != nil { 95 return "", fmt.Errorf("invalid url %s: %w", dogURL, err) 96 } 97 return fmt.Sprintf("[](%s)", src, src), nil 98 } 99 100 func (u realPack) readDog(dogURL string) (string, error) { 101 if dogURL == "" { 102 uri := string(u) 103 req, err := http.NewRequest("GET", uri, nil) 104 if err != nil { 105 return "", fmt.Errorf("could not create request %s: %w", uri, err) 106 } 107 req.Header.Add("Accept", "application/json") 108 resp, err := client.Do(req) 109 if err != nil { 110 return "", fmt.Errorf("could not read dog from %s: %w", uri, err) 111 } 112 defer resp.Body.Close() 113 var a dogResult 114 if err = json.NewDecoder(resp.Body).Decode(&a); err != nil { 115 return "", err 116 } 117 dogURL = a.URL 118 } 119 120 // GitHub doesn't support videos :( 121 if !filetypes.MatchString(dogURL) { 122 return "", errors.New("unsupported doggo :( unknown filetype: " + dogURL) 123 } 124 // checking size, GitHub doesn't support big images 125 toobig, err := github.ImageTooBig(dogURL) 126 if err != nil { 127 return "", err 128 } else if toobig { 129 return "", errors.New("unsupported doggo :( size too big: " + dogURL) 130 } 131 return FormatURL(dogURL) 132 } 133 134 func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { 135 return handle(pc.GitHubClient, pc.Logger, &e, dogURL, defaultFineImagesRoot) 136 } 137 138 func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, p pack, fineImagesRoot string) error { 139 // Only consider new comments. 140 if e.Action != github.GenericCommentActionCreated { 141 return nil 142 } 143 // Make sure they are requesting a dog 144 mat := match.FindStringSubmatch(e.Body) 145 url := "" 146 if mat == nil { 147 // check is this one of the famous.dog 148 if fineRegex.FindStringSubmatch(e.Body) != nil { 149 url = fineImagesRoot + fineIMG 150 } else if notFineRegex.FindStringSubmatch(e.Body) != nil { 151 url = fineImagesRoot + notFineIMG 152 } else if unbearableRegex.FindStringSubmatch(e.Body) != nil { 153 url = fineImagesRoot + unbearableIMG 154 } 155 156 if url == "" { 157 return nil 158 } 159 } 160 161 org := e.Repo.Owner.Login 162 repo := e.Repo.Name 163 number := e.Number 164 165 for i := 0; i < 5; i++ { 166 resp, err := p.readDog(url) 167 if err != nil { 168 log.WithError(err).Println("Failed to get dog img") 169 continue 170 } 171 return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp)) 172 } 173 174 return errors.New("could not find a valid dog image") 175 }