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 }