github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/test/integration/cmd/fakeghserver/main.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  // fakeghserver serves github API for integration tests.
    18  package main
    19  
    20  import (
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"net/http"
    25  	"strconv"
    26  	"time"
    27  
    28  	"github.com/gorilla/mux"
    29  	"github.com/sirupsen/logrus"
    30  	prowgh "sigs.k8s.io/prow/pkg/github"
    31  
    32  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    33  	"sigs.k8s.io/prow/pkg/interrupts"
    34  	"sigs.k8s.io/prow/pkg/logrusutil"
    35  	"sigs.k8s.io/prow/pkg/pjutil"
    36  )
    37  
    38  type options struct {
    39  	port int
    40  }
    41  
    42  func (o *options) validate() error {
    43  	return nil
    44  }
    45  
    46  func flagOptions() *options {
    47  	o := &options{}
    48  	flag.IntVar(&o.port, "port", 8888, "Port to listen on.")
    49  	return o
    50  }
    51  
    52  func main() {
    53  	logrusutil.ComponentInit()
    54  
    55  	o := flagOptions()
    56  	flag.Parse()
    57  	if err := o.validate(); err != nil {
    58  		logrus.WithError(err).Fatal("Invalid arguments.")
    59  	}
    60  	defer interrupts.WaitForGracefulShutdown()
    61  	ghClient := fakegithub.NewFakeClient()
    62  
    63  	r := mux.NewRouter()
    64  	// So far, supports APIs used by crier:
    65  	//type GitHubClient interface {
    66  	// 	BotName() (string, error) # /user
    67  	// 	CreateStatus(org, repo, ref string, s github.Status) error # fmt.Sprintf("/repos/%s/%s/statuses/%s", org, repo, SHA)
    68  	// 	ListIssueComments(org, repo string, number int) ([]github.IssueComment, error) # fmt.Sprintf("/repos/%s/%s/issues/%d/comments", org, repo, number)
    69  	// 	CreateComment(org, repo string, number int, comment string) error # fmt.Sprintf("/repos/%s/%s/issues/%d/comments", org, repo, number),
    70  	// 	DeleteComment(org, repo string, ID int) error # fmt.Sprintf("/repos/%s/%s/issues/comments/%d", org, repo, number),
    71  	//  EditComment(org, repo string, ID int, comment string) error # fmt.Sprintf("/repos/%s/%s/issues/comments/%d", org, repo, number),
    72  	//  GetRepoLabels(org, repo string) ([]Label, error) # fmt.Sprintf("/repos/%s/%s/labels", org, repo),
    73  	//  GetIssueLabels(org, repo string, number int) ([]Label, error) # fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number)
    74  	//  AddLabels(org, repo string, number int, labels ...string) error # fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number),
    75  	//  AddLabel(org, repo string, number int, label string) # fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number)
    76  	//  AddRepoLabel(org, repo, label, description, color string) error # fmt.Sprintf("/repos/%s/%s/labels", org, repo),
    77  	r.Path("/").Handler(response(defaultHandler()))
    78  	r.Path("/user").Handler(response(userHandler(ghClient)))
    79  	r.Path("/repos/{org}/{repo}/statuses/{sha}").Handler(response(statusHandler(ghClient)))
    80  	r.Path("/repos/{org}/{repo}/commits/{sha}/status").Queries("per_page", "{page}").Handler(response(statusHandler(ghClient)))
    81  	r.Path("/repos/{org}/{repo}/issues").Handler(response(issueHandler(ghClient)))
    82  	r.Path("/repos/{org}/{repo}/issues/{issue_id}/comments").Handler(response(issueCommentHandler(ghClient)))
    83  	r.Path("/repos/{org}/{repo}/issues/comments/${comment_id}").Handler(response(issueCommentHandler(ghClient)))
    84  	r.Path("/repos/{org}/{repo}/labels").Handler(response(labelHandler(ghClient)))
    85  	r.Path("/repos/{org}/{repo}/issues/{issue_id}/labels").Handler(response(labelHandler(ghClient)))
    86  
    87  	health := pjutil.NewHealth()
    88  	health.ServeReady()
    89  
    90  	logrus.Info("Start server")
    91  
    92  	// setup done, actually start the server
    93  	server := &http.Server{Addr: ":8888", Handler: r}
    94  	interrupts.ListenAndServe(server, 5*time.Second)
    95  }
    96  
    97  func unmarshal(r *http.Request, data interface{}) error {
    98  	d := json.NewDecoder(r.Body)
    99  	d.DisallowUnknownFields()
   100  
   101  	if err := d.Decode(&data); err != nil {
   102  		return fmt.Errorf("{\"error\": \"Failed unmarshal request: %v\"}", err.Error())
   103  	}
   104  	return nil
   105  }
   106  
   107  func response(f func(*http.Request) (interface{}, int, error)) http.Handler {
   108  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   109  		msg, statusCode, err := f(r)
   110  		logrus.Infof("request: %s - %s. responses: %s, %d, %v", r.URL.Path, r.Method, msg, statusCode, err)
   111  		if err != nil {
   112  			w.WriteHeader(http.StatusInternalServerError)
   113  			fmt.Fprint(w, err.Error())
   114  			logrus.WithError(err).Errorf("failed serving %s ( %s )", r.URL.Path, r.Method)
   115  			return
   116  		}
   117  
   118  		w.Header().Set("Content-Type", "application/json")
   119  		w.Header().Set("Link", "")
   120  		w.WriteHeader(statusCode)
   121  		fmt.Fprint(w, msg)
   122  		logrus.Info("Succeeded with request: ", statusCode)
   123  	})
   124  }
   125  
   126  func defaultHandler() func(*http.Request) (interface{}, int, error) {
   127  	return func(r *http.Request) (interface{}, int, error) {
   128  		logrus.Infof("Not supported: %s, %s", r.URL.Path, r.Method)
   129  		return "", http.StatusNotFound,
   130  			fmt.Errorf("{\"error\": \"API not supported\"}, %s, %s", r.URL.Path, r.Method)
   131  	}
   132  }
   133  
   134  func userHandler(ghc *fakegithub.FakeClient) func(*http.Request) (interface{}, int, error) {
   135  	return func(r *http.Request) (interface{}, int, error) {
   136  		logrus.Infof("Serving: %s, %s", r.URL.Path, r.Method)
   137  		userData, err := ghc.BotUser()
   138  		if err != nil {
   139  			return "", http.StatusInternalServerError, err
   140  		}
   141  		var content []byte
   142  		content, err = json.Marshal(&userData)
   143  		return string(content), http.StatusOK, err
   144  	}
   145  }
   146  
   147  func statusHandler(ghc *fakegithub.FakeClient) func(*http.Request) (interface{}, int, error) {
   148  	return func(r *http.Request) (interface{}, int, error) {
   149  		logrus.Infof("Serving: %s, %s", r.URL.Path, r.Method)
   150  		vars := mux.Vars(r)
   151  		org, repo, SHA := vars["org"], vars["repo"], vars["sha"]
   152  		if r.Method == http.MethodPost { // Create
   153  			data := prowgh.Status{}
   154  			if err := unmarshal(r, &data); err != nil {
   155  				return "", http.StatusInternalServerError, err
   156  			}
   157  			return "", http.StatusCreated, ghc.CreateStatus(org, repo, SHA, data)
   158  		}
   159  		if r.Method == http.MethodGet {
   160  			res, err := ghc.GetCombinedStatus(org, repo, SHA)
   161  			if err != nil {
   162  				return "", http.StatusInternalServerError, err
   163  			}
   164  			content, err := json.Marshal(res)
   165  			if err != nil {
   166  				return "", http.StatusInternalServerError, err
   167  			}
   168  			return string(content), http.StatusOK, nil
   169  		}
   170  		return "", http.StatusInternalServerError, fmt.Errorf("{\"error\": \"API not supported\"}, %s, %s", r.URL.Path, r.Method)
   171  	}
   172  }
   173  
   174  func issueHandler(ghc *fakegithub.FakeClient) func(*http.Request) (interface{}, int, error) {
   175  	return func(r *http.Request) (interface{}, int, error) {
   176  		logrus.Infof("Serving: %s, %s", r.URL.Path, r.Method)
   177  		vars := mux.Vars(r)
   178  		org, repo := vars["org"], vars["repo"]
   179  		data := prowgh.Issue{}
   180  		if err := unmarshal(r, &data); err != nil {
   181  			return "", http.StatusInternalServerError, err
   182  		}
   183  		id, err := ghc.CreateIssue(org, repo, data.Title, data.Body, data.Milestone.Number, nil, nil)
   184  		return fmt.Sprintf(`{"number": %d}`, id), http.StatusCreated, err
   185  	}
   186  }
   187  
   188  func issueCommentHandler(ghc *fakegithub.FakeClient) func(*http.Request) (interface{}, int, error) {
   189  	return func(r *http.Request) (interface{}, int, error) {
   190  		logrus.Infof("Serving: %s, %s", r.URL.Path, r.Method)
   191  		vars := mux.Vars(r)
   192  		org, repo := vars["org"], vars["repo"]
   193  		if issueID, exist := vars["issue_id"]; exist {
   194  			id, err := strconv.Atoi(issueID)
   195  			if err != nil {
   196  				return "", http.StatusUnprocessableEntity, err
   197  			}
   198  			if r.Method == http.MethodGet { // List
   199  				var issues []prowgh.IssueComment
   200  				issues, err = ghc.ListIssueComments(org, repo, id)
   201  				if err != nil {
   202  					return "", http.StatusInternalServerError, err
   203  				}
   204  				var content []byte
   205  				content, err = json.Marshal(issues)
   206  				return string(content), http.StatusOK, err
   207  			}
   208  			if r.Method == http.MethodPost { // Create
   209  				data := prowgh.IssueComment{}
   210  				if err = unmarshal(r, &data); err != nil {
   211  					return "", http.StatusUnprocessableEntity, err
   212  				}
   213  				return "", http.StatusCreated, ghc.CreateComment(org, repo, id, data.Body)
   214  			}
   215  		}
   216  		if commentID, exist := vars["comment_id"]; exist {
   217  			var id int
   218  			id, err := strconv.Atoi(commentID)
   219  			if err != nil {
   220  				return "", http.StatusUnprocessableEntity, err
   221  			}
   222  			if r.Method == http.MethodDelete { // Delete
   223  				return "", http.StatusOK, ghc.DeleteComment(org, repo, id)
   224  			}
   225  			if r.Method == http.MethodPatch { // Edit
   226  				content := &prowgh.IssueComment{}
   227  				if err := unmarshal(r, content); err != nil {
   228  					return "", http.StatusUnprocessableEntity, err
   229  				}
   230  				return "", http.StatusOK, ghc.EditComment(org, repo, id, content.Body)
   231  			}
   232  		}
   233  		return "", http.StatusInternalServerError, fmt.Errorf("{\"error\": \"API not supported\"}, %s, %s", r.URL.Path, r.Method)
   234  	}
   235  }
   236  
   237  // GetRepoLabels(org, repo string) ([]Label, error) # fmt.Sprintf("/repos/%s/%s/labels", org, repo),
   238  // GetIssueLabels(org, repo string, number int) ([]Label, error) # fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number)
   239  // AddLabel(org, repo string, number int, label string) # fmt.Sprintf("/repos/%s/%s/issues/%d/labels", org, repo, number)
   240  // AddRepoLabel(org, repo, label, description, color string) error # fmt.Sprintf("/repos/%s/%s/labels", org, repo),
   241  func labelHandler(ghc *fakegithub.FakeClient) func(*http.Request) (interface{}, int, error) {
   242  	return func(r *http.Request) (interface{}, int, error) {
   243  		logrus.Infof("Serving: %s, %s", r.URL.Path, r.Method)
   244  		vars := mux.Vars(r)
   245  		org, repo := vars["org"], vars["repo"]
   246  		if issueID, exist := vars["issue_id"]; exist { // Issue label
   247  			id, err := strconv.Atoi(issueID)
   248  			if err != nil {
   249  				return "", http.StatusUnprocessableEntity, err
   250  			}
   251  			if r.Method == http.MethodGet { // List
   252  				var labels []prowgh.Label
   253  				labels, err = ghc.GetIssueLabels(org, repo, id)
   254  				if err != nil {
   255  					return "", http.StatusInternalServerError, err
   256  				}
   257  				var content []byte
   258  				content, err = json.Marshal(labels)
   259  				return string(content), http.StatusOK, err
   260  			}
   261  			if r.Method == http.MethodPost { // Create
   262  				var labels []string
   263  				if err = unmarshal(r, &labels); err != nil {
   264  					return "", http.StatusUnprocessableEntity, err
   265  				}
   266  				return "", http.StatusCreated, ghc.AddLabels(org, repo, id, labels...)
   267  			}
   268  		} else if r.Method == http.MethodGet { // List repo labels
   269  			var labels []prowgh.Label
   270  			labels, err := ghc.GetRepoLabels(org, repo)
   271  			if err != nil {
   272  				return "", http.StatusInternalServerError, err
   273  			}
   274  			var content []byte
   275  			content, err = json.Marshal(labels)
   276  			return string(content), http.StatusOK, err
   277  		} else if r.Method == http.MethodPost { // Create repo label
   278  			data := prowgh.Label{}
   279  			if err := unmarshal(r, &data); err != nil {
   280  				return "", http.StatusUnprocessableEntity, err
   281  			}
   282  			return "", http.StatusCreated, ghc.AddRepoLabel(org, repo, data.Name, data.Description, data.Color)
   283  		}
   284  		return "", http.StatusInternalServerError, fmt.Errorf("{\"error\": \"API not supported\"}, %s, %s", r.URL.Path, r.Method)
   285  	}
   286  }