github.com/argoproj/argo-events@v1.9.1/eventsources/sources/gerrit/start.go (about)

     1  /*
     2  Copyright 2018 BlackRock, Inc.
     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 gerrit
    18  
    19  import (
    20  	"context"
    21  	"crypto/rand"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"math/big"
    26  	"net/http"
    27  	"time"
    28  
    29  	"github.com/andygrunwald/go-gerrit"
    30  	"go.uber.org/zap"
    31  
    32  	"github.com/argoproj/argo-events/common"
    33  	"github.com/argoproj/argo-events/common/logging"
    34  	eventsourcecommon "github.com/argoproj/argo-events/eventsources/common"
    35  	"github.com/argoproj/argo-events/eventsources/common/webhook"
    36  	"github.com/argoproj/argo-events/eventsources/sources"
    37  	"github.com/argoproj/argo-events/pkg/apis/events"
    38  )
    39  
    40  // controller controls the webhook operations
    41  var (
    42  	controller = webhook.NewController()
    43  )
    44  
    45  // set up the activation and inactivation channels to control the state of routes.
    46  func init() {
    47  	go webhook.ProcessRouteStatus(controller)
    48  }
    49  
    50  // Implement Router
    51  // 1. GetRoute
    52  // 2. HandleRoute
    53  // 3. PostActivate
    54  // 4. PostDeactivate
    55  
    56  // GetRoute returns the route
    57  func (router *Router) GetRoute() *webhook.Route {
    58  	return router.route
    59  }
    60  
    61  // HandleRoute handles incoming requests on the route
    62  func (router *Router) HandleRoute(writer http.ResponseWriter, request *http.Request) {
    63  	route := router.GetRoute()
    64  	logger := route.Logger.With(
    65  		logging.LabelEndpoint, route.Context.Endpoint,
    66  		logging.LabelPort, route.Context.Port,
    67  		logging.LabelHTTPMethod, route.Context.Method,
    68  	)
    69  
    70  	logger.Info("received a request, processing it...")
    71  
    72  	if !route.Active {
    73  		logger.Info("endpoint is not active, won't process the request")
    74  		common.SendErrorResponse(writer, "inactive endpoint")
    75  		return
    76  	}
    77  
    78  	defer func(start time.Time) {
    79  		route.Metrics.EventProcessingDuration(route.EventSourceName, route.EventName, float64(time.Since(start)/time.Millisecond))
    80  	}(time.Now())
    81  
    82  	request.Body = http.MaxBytesReader(writer, request.Body, route.Context.GetMaxPayloadSize())
    83  	body, err := io.ReadAll(request.Body)
    84  	if err != nil {
    85  		logger.Errorw("failed to parse request body", zap.Error(err))
    86  		common.SendErrorResponse(writer, err.Error())
    87  		route.Metrics.EventProcessingFailed(route.EventSourceName, route.EventName)
    88  		return
    89  	}
    90  
    91  	event := &events.GerritEventData{
    92  		Headers:  request.Header,
    93  		Body:     (*json.RawMessage)(&body),
    94  		Metadata: router.gerritEventSource.Metadata,
    95  	}
    96  
    97  	eventBody, err := json.Marshal(event)
    98  	if err != nil {
    99  		logger.Info("failed to marshal event")
   100  		common.SendErrorResponse(writer, "invalid event")
   101  		route.Metrics.EventProcessingFailed(route.EventSourceName, route.EventName)
   102  		return
   103  	}
   104  
   105  	logger.Info("dispatching event on route's data channel")
   106  	route.DataCh <- eventBody
   107  
   108  	logger.Info("request successfully processed")
   109  	common.SendSuccessResponse(writer, "success")
   110  }
   111  
   112  // PostActivate performs operations once the route is activated and ready to consume requests
   113  func (router *Router) PostActivate() error {
   114  	return nil
   115  }
   116  
   117  // PostInactivate performs operations after the route is inactivated
   118  func (router *Router) PostInactivate() error {
   119  	gerritEventSource := router.gerritEventSource
   120  	if !gerritEventSource.NeedToCreateHooks() || !gerritEventSource.DeleteHookOnFinish {
   121  		return nil
   122  	}
   123  
   124  	logger := router.route.Logger
   125  	logger.Info("deleting Gerrit hooks...")
   126  
   127  	for _, p := range gerritEventSource.Projects {
   128  		_, ok := router.projectHooks[p]
   129  		if !ok {
   130  			return fmt.Errorf("can not find hook ID for project %s", p)
   131  		}
   132  		if err := router.gerritHookService.Delete(p, gerritEventSource.HookName); err != nil {
   133  			return fmt.Errorf("failed to delete hook for project %s. err: %w", p, err)
   134  		}
   135  		logger.Infof("Gerrit hook deleted for project %s", p)
   136  	}
   137  	return nil
   138  }
   139  
   140  // StartListening starts an event source
   141  func (el *EventListener) StartListening(ctx context.Context, dispatch func([]byte, ...eventsourcecommon.Option) error) error {
   142  	logger := logging.FromContext(ctx).
   143  		With(logging.LabelEventSourceType, el.GetEventSourceType(), logging.LabelEventName, el.GetEventName())
   144  	logger.Info("started processing the Gerrit event source...")
   145  
   146  	defer sources.Recover(el.GetEventName())
   147  
   148  	gerritEventSource := &el.GerritEventSource
   149  
   150  	route := webhook.NewRoute(gerritEventSource.Webhook, logger, el.GetEventSourceName(), el.GetEventName(), el.Metrics)
   151  	router := &Router{
   152  		route:             route,
   153  		gerritEventSource: gerritEventSource,
   154  		projectHooks:      make(map[string]string),
   155  	}
   156  
   157  	if gerritEventSource.NeedToCreateHooks() {
   158  		// In order to set up a hook for the Gerrit project,
   159  		// 1. Set up Gerrit client with basic auth
   160  		// 2. Configure Hook with given event type
   161  		// 3. Create project hook
   162  
   163  		logger.Info("retrieving the access token credentials...")
   164  
   165  		formattedURL := common.FormattedURL(gerritEventSource.Webhook.URL, gerritEventSource.Webhook.Endpoint)
   166  		opt := &ProjectHookConfigs{
   167  			URL:       formattedURL,
   168  			Events:    router.gerritEventSource.Events,
   169  			SslVerify: router.gerritEventSource.SslVerify,
   170  		}
   171  
   172  		logger.Info("setting up the client to connect to Gerrit...")
   173  		var err error
   174  		router.gerritClient, err = gerrit.NewClient(router.gerritEventSource.GerritBaseURL, nil)
   175  		if err != nil {
   176  			return fmt.Errorf("failed to initialize client, %w", err)
   177  		}
   178  		if gerritEventSource.Auth != nil {
   179  			username, err := common.GetSecretFromVolume(gerritEventSource.Auth.Username)
   180  			if err != nil {
   181  				return fmt.Errorf("username not found, %w", err)
   182  			}
   183  			password, err := common.GetSecretFromVolume(gerritEventSource.Auth.Password)
   184  			if err != nil {
   185  				return fmt.Errorf("password not found, %w", err)
   186  			}
   187  			router.gerritClient.Authentication.SetBasicAuth(username, password)
   188  		}
   189  		router.gerritHookService = newGerritWebhookService(router.gerritClient)
   190  
   191  		f := func() {
   192  			for _, p := range gerritEventSource.Projects {
   193  				hooks, err := router.gerritHookService.List(p)
   194  				if err != nil {
   195  					logger.Errorf("failed to list existing webhooks of project %s. err: %+v", p, err)
   196  					continue
   197  				}
   198  				// hook already exist
   199  				if h, ok := hooks[gerritEventSource.HookName]; ok {
   200  					if h.URL == formattedURL {
   201  						router.projectHooks[p] = gerritEventSource.HookName
   202  						continue
   203  					}
   204  				}
   205  				logger.Infof("hook not found for project %s, creating ...", p)
   206  				if _, err := router.gerritHookService.Create(p, gerritEventSource.HookName, opt); err != nil {
   207  					logger.Errorf("failed to create gerrit webhook for project %s. err: %+v", p, err)
   208  					continue
   209  				}
   210  				router.projectHooks[p] = gerritEventSource.HookName
   211  				time.Sleep(500 * time.Millisecond)
   212  			}
   213  		}
   214  
   215  		// Mitigate race condtions - it might create multiple hooks with same config when replicas > 1
   216  		randomNum, _ := rand.Int(rand.Reader, big.NewInt(int64(2000)))
   217  		time.Sleep(time.Duration(randomNum.Int64()) * time.Millisecond)
   218  		f()
   219  
   220  		ctx, cancel := context.WithCancel(ctx)
   221  		defer cancel()
   222  
   223  		go func() {
   224  			// Another kind of race conditions might happen when pods do rolling upgrade - new pod starts
   225  			// and old pod terminates, if DeleteHookOnFinish is true, the hook will be deleted from gerrit.
   226  			// This is a workround to mitigate the race conditions.
   227  			logger.Info("starting gerrit hooks manager daemon")
   228  			ticker := time.NewTicker(60 * time.Second)
   229  			defer ticker.Stop()
   230  			for {
   231  				select {
   232  				case <-ctx.Done():
   233  					logger.Info("exiting gerrit hooks manager daemon")
   234  					return
   235  				case <-ticker.C:
   236  					f()
   237  				}
   238  			}
   239  		}()
   240  	} else {
   241  		logger.Info("no need to create webhooks")
   242  	}
   243  
   244  	return webhook.ManageRoute(ctx, router, controller, dispatch)
   245  }