158 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			158 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| package actionsmetrics
 | |
| 
 | |
| /*
 | |
| Copyright 2022 The actions-runner-controller authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 
 | |
| 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 | |
| 
 | |
| 	"github.com/go-logr/logr"
 | |
| 	gogithub "github.com/google/go-github/v52/github"
 | |
| 	ctrl "sigs.k8s.io/controller-runtime"
 | |
| 
 | |
| 	"github.com/actions/actions-runner-controller/github"
 | |
| )
 | |
| 
 | |
| type EventHook func(interface{})
 | |
| 
 | |
| // WebhookServer is a HTTP server that handles workflow_job events sent from GitHub Actions
 | |
| type WebhookServer struct {
 | |
| 	Log logr.Logger
 | |
| 
 | |
| 	// SecretKeyBytes is the byte representation of the Webhook secret token
 | |
| 	// the administrator is generated and specified in GitHub Web UI.
 | |
| 	SecretKeyBytes []byte
 | |
| 
 | |
| 	// GitHub Client to discover runner groups assigned to a repository
 | |
| 	GitHubClient *github.Client
 | |
| 
 | |
| 	// When HorizontalRunnerAutoscalerGitHubWebhook handles a request, each EventHook is sent the webhook event
 | |
| 	EventHooks []EventHook
 | |
| }
 | |
| 
 | |
| func (autoscaler *WebhookServer) Reconcile(_ context.Context, request reconcile.Request) (reconcile.Result, error) {
 | |
| 	return ctrl.Result{}, nil
 | |
| }
 | |
| 
 | |
| func (autoscaler *WebhookServer) Handle(w http.ResponseWriter, r *http.Request) {
 | |
| 	var (
 | |
| 		ok bool
 | |
| 
 | |
| 		err error
 | |
| 	)
 | |
| 
 | |
| 	defer func() {
 | |
| 		if !ok {
 | |
| 			w.WriteHeader(http.StatusInternalServerError)
 | |
| 
 | |
| 			if err != nil {
 | |
| 				msg := err.Error()
 | |
| 				if written, err := w.Write([]byte(msg)); err != nil {
 | |
| 					autoscaler.Log.V(1).Error(err, "failed writing http error response", "msg", msg, "written", written)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	defer func() {
 | |
| 		if r.Body != nil {
 | |
| 			r.Body.Close()
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// respond ok to GET / e.g. for health check
 | |
| 	if r.Method == http.MethodGet {
 | |
| 		ok = true
 | |
| 		fmt.Fprintln(w, "actions-metrics-server is running")
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	var payload []byte
 | |
| 
 | |
| 	if len(autoscaler.SecretKeyBytes) > 0 {
 | |
| 		payload, err = gogithub.ValidatePayload(r, autoscaler.SecretKeyBytes)
 | |
| 		if err != nil {
 | |
| 			autoscaler.Log.Error(err, "error validating request body")
 | |
| 
 | |
| 			return
 | |
| 		}
 | |
| 	} else {
 | |
| 		payload, err = io.ReadAll(r.Body)
 | |
| 		if err != nil {
 | |
| 			autoscaler.Log.Error(err, "error reading request body")
 | |
| 
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	webhookType := gogithub.WebHookType(r)
 | |
| 	event, err := gogithub.ParseWebHook(webhookType, payload)
 | |
| 	if err != nil {
 | |
| 		var s string
 | |
| 		if payload != nil {
 | |
| 			s = string(payload)
 | |
| 		}
 | |
| 
 | |
| 		autoscaler.Log.Error(err, "could not parse webhook", "webhookType", webhookType, "payload", s)
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	log := autoscaler.Log.WithValues(
 | |
| 		"event", webhookType,
 | |
| 		"hookID", r.Header.Get("X-GitHub-Hook-ID"),
 | |
| 		"delivery", r.Header.Get("X-GitHub-Delivery"),
 | |
| 	)
 | |
| 
 | |
| 	switch event.(type) {
 | |
| 	case *gogithub.PingEvent:
 | |
| 		ok = true
 | |
| 
 | |
| 		w.WriteHeader(http.StatusOK)
 | |
| 
 | |
| 		msg := "pong"
 | |
| 
 | |
| 		if written, err := w.Write([]byte(msg)); err != nil {
 | |
| 			log.Error(err, "failed writing http response", "msg", msg, "written", written)
 | |
| 		}
 | |
| 
 | |
| 		log.Info("handled ping event")
 | |
| 
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for _, eventHook := range autoscaler.EventHooks {
 | |
| 		eventHook(event)
 | |
| 	}
 | |
| 
 | |
| 	ok = true
 | |
| 
 | |
| 	w.WriteHeader(http.StatusOK)
 | |
| 
 | |
| 	msg := "ok"
 | |
| 
 | |
| 	log.Info(msg)
 | |
| 
 | |
| 	if written, err := w.Write([]byte(msg)); err != nil {
 | |
| 		log.Error(err, "failed writing http response", "msg", msg, "written", written)
 | |
| 	}
 | |
| }
 |