github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/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("[![dog image](%s)](%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  }