diff --git a/applicationset/webhook/webhook.go b/applicationset/webhook/webhook.go index 30f1a2eb1ad0e..4fb4d6668bc2f 100644 --- a/applicationset/webhook/webhook.go +++ b/applicationset/webhook/webhook.go @@ -10,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "sync" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" @@ -26,9 +27,12 @@ import ( log "github.com/sirupsen/logrus" ) +const payloadQueueSize = 50000 + var errBasicAuthVerificationFailed = errors.New("basic auth verification failed") type WebhookHandler struct { + sync.WaitGroup // for testing namespace string github *github.Webhook gitlab *gitlab.Webhook @@ -36,6 +40,7 @@ type WebhookHandler struct { azuredevopsAuthHandler func(r *http.Request) error client client.Client generators map[string]generators.Generator + queue chan interface{} } type gitGeneratorInfo struct { @@ -66,7 +71,7 @@ type prGeneratorGitlabInfo struct { APIHostname string } -func NewWebhookHandler(namespace string, argocdSettingsMgr *argosettings.SettingsManager, client client.Client, generators map[string]generators.Generator) (*WebhookHandler, error) { +func NewWebhookHandler(namespace string, webhookParallelism int, argocdSettingsMgr *argosettings.SettingsManager, client client.Client, generators map[string]generators.Generator) (*WebhookHandler, error) { // register the webhook secrets stored under "argocd-secret" for verifying incoming payloads argocdSettings, err := argocdSettingsMgr.GetSettings() if err != nil { @@ -94,7 +99,7 @@ func NewWebhookHandler(namespace string, argocdSettingsMgr *argosettings.Setting return nil } - return &WebhookHandler{ + webhookHandler := &WebhookHandler{ namespace: namespace, github: githubHandler, gitlab: gitlabHandler, @@ -102,7 +107,28 @@ func NewWebhookHandler(namespace string, argocdSettingsMgr *argosettings.Setting azuredevopsAuthHandler: azuredevopsAuthHandler, client: client, generators: generators, - }, nil + queue: make(chan interface{}, payloadQueueSize), + } + + webhookHandler.startWorkerPool(webhookParallelism) + + return webhookHandler, nil +} + +func (h *WebhookHandler) startWorkerPool(webhookParallelism int) { + for i := 0; i < webhookParallelism; i++ { + h.Add(1) + go func() { + defer h.Done() + for { + payload, ok := <-h.queue + if !ok { + return + } + h.HandleEvent(payload) + } + }() + } } func (h *WebhookHandler) HandleEvent(payload interface{}) { @@ -176,7 +202,12 @@ func (h *WebhookHandler) Handler(w http.ResponseWriter, r *http.Request) { return } - h.HandleEvent(payload) + select { + case h.queue <- payload: + default: + log.Info("Queue is full, discarding webhook payload") + http.Error(w, "Queue is full, discarding webhook payload", http.StatusServiceUnavailable) + } } func parseRevision(ref string) string { diff --git a/applicationset/webhook/webhook_test.go b/applicationset/webhook/webhook_test.go index 14fc5ce68503b..683928635bd51 100644 --- a/applicationset/webhook/webhook_test.go +++ b/applicationset/webhook/webhook_test.go @@ -178,6 +178,7 @@ func TestWebhookHandler(t *testing.T) { } namespace := "test" + webhookParallelism := 10 fakeClient := newFakeClient(namespace) scheme := runtime.NewScheme() err := v1alpha1.AddToScheme(scheme) @@ -206,7 +207,7 @@ func TestWebhookHandler(t *testing.T) { fakeAppWithMergeAndNestedGitGenerator("merge-nested-git-github", namespace, "https://github.com/org/repo"), ).Build() set := argosettings.NewSettingsManager(context.TODO(), fakeClient, namespace) - h, err := NewWebhookHandler(namespace, set, fc, mockGenerators()) + h, err := NewWebhookHandler(namespace, webhookParallelism, set, fc, mockGenerators()) require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil) @@ -217,6 +218,8 @@ func TestWebhookHandler(t *testing.T) { w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, test.expectedStatusCode, w.Code) list := &v1alpha1.ApplicationSetList{} diff --git a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go index ddc6709d0b739..dc79a1d38c403 100644 --- a/cmd/argocd-applicationset-controller/commands/applicationset_controller.go +++ b/cmd/argocd-applicationset-controller/commands/applicationset_controller.go @@ -69,6 +69,7 @@ func NewCommand() *cobra.Command { globalPreservedAnnotations []string globalPreservedLabels []string enableScmProviders bool + webhookParallelism int ) scheme := runtime.NewScheme() _ = clientgoscheme.AddToScheme(scheme) @@ -182,7 +183,7 @@ func NewCommand() *cobra.Command { topLevelGenerators := generators.GetGenerators(ctx, mgr.GetClient(), k8sClient, namespace, argoCDService, dynamicClient, scmConfig) // start a webhook server that listens to incoming webhook payloads - webhookHandler, err := webhook.NewWebhookHandler(namespace, argoSettingsMgr, mgr.GetClient(), topLevelGenerators) + webhookHandler, err := webhook.NewWebhookHandler(namespace, webhookParallelism, argoSettingsMgr, mgr.GetClient(), topLevelGenerators) if err != nil { log.Error(err, "failed to create webhook handler") } @@ -248,6 +249,7 @@ func NewCommand() *cobra.Command { command.Flags().StringVar(&scmRootCAPath, "scm-root-ca-path", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH", ""), "Provide Root CA Path for self-signed TLS Certificates") command.Flags().StringSliceVar(&globalPreservedAnnotations, "preserved-annotations", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_ANNOTATIONS", []string{}, ","), "Sets global preserved field values for annotations") command.Flags().StringSliceVar(&globalPreservedLabels, "preserved-labels", env.StringsFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_LABELS", []string{}, ","), "Sets global preserved field values for labels") + command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently") return &command } diff --git a/cmd/argocd-server/commands/argocd_server.go b/cmd/argocd-server/commands/argocd_server.go index e3ba8141c51c8..552de22909bea 100644 --- a/cmd/argocd-server/commands/argocd_server.go +++ b/cmd/argocd-server/commands/argocd_server.go @@ -82,6 +82,7 @@ func NewCommand() *cobra.Command { staticAssetsDir string applicationNamespaces []string enableProxyExtension bool + webhookParallelism int // ApplicationSet enableNewGitFileGlobbing bool @@ -221,6 +222,7 @@ func NewCommand() *cobra.Command { StaticAssetsDir: staticAssetsDir, ApplicationNamespaces: applicationNamespaces, EnableProxyExtension: enableProxyExtension, + WebhookParallelism: webhookParallelism, } appsetOpts := server.ApplicationSetOpts{ @@ -294,6 +296,7 @@ func NewCommand() *cobra.Command { command.Flags().BoolVar(&dexServerStrictTLS, "dex-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to dex server") command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces where application resources can be managed in") command.Flags().BoolVar(&enableProxyExtension, "enable-proxy-extension", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_PROXY_EXTENSION", false), "Enable Proxy Extension feature") + command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently") // Flags related to the applicationSet component. command.Flags().StringVar(&scmRootCAPath, "appset-scm-root-ca-path", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH", ""), "Provide Root CA Path for self-signed TLS Certificates") diff --git a/docs/operator-manual/argocd-cmd-params-cm.yaml b/docs/operator-manual/argocd-cmd-params-cm.yaml index ae2072a18fb53..a3688d0425d57 100644 --- a/docs/operator-manual/argocd-cmd-params-cm.yaml +++ b/docs/operator-manual/argocd-cmd-params-cm.yaml @@ -93,6 +93,8 @@ data: # Semicolon-separated list of content types allowed on non-GET requests. Set an empty string to allow all. Be aware # that allowing content types besides application/json may make your API more vulnerable to CSRF attacks. server.api.content.types: "application/json" + # Number of webhook requests processed concurrently (default 50) + server.webhook.parallelism.limit: "50" # Set the logging format. One of: text|json (default "text") server.log.format: "text" @@ -213,6 +215,8 @@ data: applicationsetcontroller.allowed.scm.providers: "https://git.example.com/,https://gitlab.example.com/" # To disable SCM providers entirely (i.e. disable the SCM and PR generators), set this to "false". Default is "true". applicationsetcontroller.enable.scm.providers: "false" + # Number of webhook requests processed concurrently (default 50) + applicationsetcontroller.webhook.parallelism.limit: "50" ## Argo CD Notifications Controller Properties # Set the logging level. One of: debug|info|warn|error (default "info") diff --git a/docs/operator-manual/server-commands/argocd-server.md b/docs/operator-manual/server-commands/argocd-server.md index 882603d080d89..2f022f3da2be0 100644 --- a/docs/operator-manual/server-commands/argocd-server.md +++ b/docs/operator-manual/server-commands/argocd-server.md @@ -110,6 +110,7 @@ argocd-server [flags] --token string Bearer token for authentication to the API server --user string The name of the kubeconfig user to use --username string Username for basic authentication to the API server + --webhook-parallelism-limit int Number of webhook requests processed concurrently (default 50) --x-frame-options value Set X-Frame-Options header in HTTP responses to value. To disable, set to "". (default "sameorigin") ``` diff --git a/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml b/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml index b24124ccb833f..6bade745f76c1 100644 --- a/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml +++ b/manifests/base/applicationset-controller/argocd-applicationset-controller-deployment.yaml @@ -157,6 +157,12 @@ spec: name: argocd-cmd-params-cm key: applicationsetcontroller.enable.scm.providers optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + name: argocd-cmd-params-cm + key: applicationsetcontroller.webhook.parallelism.limit + optional: true volumeMounts: - mountPath: /app/config/ssh name: ssh-known-hosts diff --git a/manifests/base/server/argocd-server-deployment.yaml b/manifests/base/server/argocd-server-deployment.yaml index b9486e7b4b2c6..24104cb7e026d 100644 --- a/manifests/base/server/argocd-server-deployment.yaml +++ b/manifests/base/server/argocd-server-deployment.yaml @@ -262,6 +262,12 @@ spec: name: argocd-cmd-params-cm key: server.api.content.types optional: true + - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + name: argocd-cmd-params-cm + key: server.webhook.parallelism.limit + optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING valueFrom: configMapKeyRef: diff --git a/manifests/core-install.yaml b/manifests/core-install.yaml index 63de862dd0029..33af6be5d3b01 100644 --- a/manifests/core-install.yaml +++ b/manifests/core-install.yaml @@ -21268,6 +21268,12 @@ spec: key: applicationsetcontroller.enable.scm.providers name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index 31c131e973b7f..6a533553a0e18 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -22609,6 +22609,12 @@ spec: key: applicationsetcontroller.enable.scm.providers name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller @@ -23590,6 +23596,12 @@ spec: key: server.api.content.types name: argocd-cmd-params-cm optional: true + - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: server.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING valueFrom: configMapKeyRef: diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index afedf6b961f04..e62a9afb57586 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -1686,6 +1686,12 @@ spec: key: applicationsetcontroller.enable.scm.providers name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller @@ -2667,6 +2673,12 @@ spec: key: server.api.content.types name: argocd-cmd-params-cm optional: true + - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: server.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING valueFrom: configMapKeyRef: diff --git a/manifests/install.yaml b/manifests/install.yaml index b56f483cf97a4..bfeeb639d2645 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -21726,6 +21726,12 @@ spec: key: applicationsetcontroller.enable.scm.providers name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller @@ -22658,6 +22664,12 @@ spec: key: server.api.content.types name: argocd-cmd-params-cm optional: true + - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: server.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING valueFrom: configMapKeyRef: diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 0cc94b9415b89..d062411bb42b8 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -803,6 +803,12 @@ spec: key: applicationsetcontroller.enable.scm.providers name: argocd-cmd-params-cm optional: true + - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: applicationsetcontroller.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true image: quay.io/argoproj/argocd:latest imagePullPolicy: Always name: argocd-applicationset-controller @@ -1735,6 +1741,12 @@ spec: key: server.api.content.types name: argocd-cmd-params-cm optional: true + - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT + valueFrom: + configMapKeyRef: + key: server.webhook.parallelism.limit + name: argocd-cmd-params-cm + optional: true - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING valueFrom: configMapKeyRef: diff --git a/server/server.go b/server/server.go index 77e058afae959..23520ee8c90fc 100644 --- a/server/server.go +++ b/server/server.go @@ -228,6 +228,7 @@ type ArgoCDServerOpts struct { ContentSecurityPolicy string ApplicationNamespaces []string EnableProxyExtension bool + WebhookParallelism int } type ApplicationSetOpts struct { @@ -1072,7 +1073,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl // Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them) argoDB := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset) - acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB) + acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.ArgoCDServerOpts.WebhookParallelism, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB) mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler) diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go index b20657fb4567b..14d602dcb9f37 100644 --- a/util/webhook/webhook.go +++ b/util/webhook/webhook.go @@ -9,6 +9,7 @@ import ( "net/url" "regexp" "strings" + "sync" "github.com/go-playground/webhooks/v6/azuredevops" "github.com/go-playground/webhooks/v6/bitbucket" @@ -41,12 +42,15 @@ type settingsSource interface { // https://github.com/shadow-maint/shadow/blob/master/libmisc/chkname.c#L36 const usernameRegex = `[a-zA-Z0-9_\.][a-zA-Z0-9_\.-]{0,30}[a-zA-Z0-9_\.\$-]?` +const payloadQueueSize = 50000 + var ( _ settingsSource = &settings.SettingsManager{} errBasicAuthVerificationFailed = errors.New("basic auth verification failed") ) type ArgoCDWebhookHandler struct { + sync.WaitGroup // for testing repoCache *cache.Cache serverCache *servercache.Cache db db.ArgoDB @@ -61,9 +65,10 @@ type ArgoCDWebhookHandler struct { azuredevopsAuthHandler func(r *http.Request) error gogs *gogs.Webhook settingsSrc settingsSource + queue chan interface{} } -func NewHandler(namespace string, applicationNamespaces []string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler { +func NewHandler(namespace string, applicationNamespaces []string, webhookParallelism int, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler { githubWebhook, err := github.New(github.Options.Secret(set.WebhookGitHubSecret)) if err != nil { log.Warnf("Unable to init the GitHub webhook") @@ -113,11 +118,30 @@ func NewHandler(namespace string, applicationNamespaces []string, appClientset a repoCache: repoCache, serverCache: serverCache, db: argoDB, + queue: make(chan interface{}, payloadQueueSize), } + acdWebhook.startWorkerPool(webhookParallelism) + return &acdWebhook } +func (a *ArgoCDWebhookHandler) startWorkerPool(webhookParallelism int) { + for i := 0; i < webhookParallelism; i++ { + a.Add(1) + go func() { + defer a.Done() + for { + payload, ok := <-a.queue + if !ok { + return + } + a.HandleEvent(payload) + } + }() + } +} + func parseRevision(ref string) string { refParts := strings.SplitN(ref, "/", 3) return refParts[len(refParts)-1] @@ -444,5 +468,10 @@ func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) { return } - a.HandleEvent(payload) + select { + case a.queue <- payload: + default: + log.Info("Queue is full, discarding webhook payload") + http.Error(w, "Queue is full, discarding webhook payload", http.StatusServiceUnavailable) + } } diff --git a/util/webhook/webhook_test.go b/util/webhook/webhook_test.go index 2e00e599fce40..dd4b2399e334e 100644 --- a/util/webhook/webhook_test.go +++ b/util/webhook/webhook_test.go @@ -67,7 +67,7 @@ func NewMockHandler(reactor *reactorDef, applicationNamespaces []string, objects } cacheClient := cacheutil.NewCache(cacheutil.NewInMemoryCache(1 * time.Hour)) - return NewHandler("argocd", applicationNamespaces, appClientset, &settings.ArgoCDSettings{}, &fakeSettingsSrc{}, cache.NewCache( + return NewHandler("argocd", applicationNamespaces, 10, appClientset, &settings.ArgoCDSettings{}, &fakeSettingsSrc{}, cache.NewCache( cacheClient, 1*time.Minute, 1*time.Minute, @@ -85,6 +85,8 @@ func TestGitHubCommitEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: https://github.com/jessesuen/test-repo, revision: master, touchedHead: true" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -101,6 +103,8 @@ func TestAzureDevOpsCommitEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: https://dev.azure.com/alexander0053/alex-test/_git/alex-test, revision: master, touchedHead: true" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -156,6 +160,8 @@ func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Requested app 'app-to-refresh' refresh" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -237,6 +243,8 @@ func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) logMessages := make([]string, 0, len(hook.Entries)) @@ -269,6 +277,8 @@ func TestGitHubTagEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: https://github.com/jessesuen/test-repo, revision: v1.0, touchedHead: false" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -285,6 +295,8 @@ func TestGitHubPingEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Ignoring webhook event" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -301,6 +313,8 @@ func TestBitbucketServerRepositoryReferenceChangedEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResultSsh := "Received push event repo: ssh://git@bitbucketserver:7999/myproject/test-repo.git, revision: master, touchedHead: true" assert.Equal(t, expectedLogResultSsh, hook.AllEntries()[len(hook.AllEntries())-2].Message) @@ -317,6 +331,8 @@ func TestBitbucketServerRepositoryDiagnosticPingEvent(t *testing.T) { req.Header.Set("X-Event-Key", "diagnostics:ping") w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Ignoring webhook event" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -333,6 +349,8 @@ func TestGogsPushEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: http://gogs-server/john/repo-test, revision: master, touchedHead: true" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -349,6 +367,8 @@ func TestGitLabPushEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: https://gitlab/group/name, revision: master, touchedHead: true" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -365,6 +385,8 @@ func TestGitLabSystemEvent(t *testing.T) { req.Body = io.NopCloser(bytes.NewReader(eventJSON)) w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusOK, w.Code) expectedLogResult := "Received push event repo: https://gitlab/group/name, revision: master, touchedHead: true" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -378,6 +400,8 @@ func TestInvalidMethod(t *testing.T) { req.Header.Set("X-GitHub-Event", "push") w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusMethodNotAllowed, w.Code) expectedLogResult := "Webhook processing failed: invalid HTTP Method" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -392,6 +416,8 @@ func TestInvalidEvent(t *testing.T) { req.Header.Set("X-GitHub-Event", "push") w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusBadRequest, w.Code) expectedLogResult := "Webhook processing failed: error parsing payload" assert.Equal(t, expectedLogResult, hook.LastEntry().Message) @@ -406,6 +432,8 @@ func TestUnknownEvent(t *testing.T) { req.Header.Set("X-Unknown-Event", "push") w := httptest.NewRecorder() h.Handler(w, req) + close(h.queue) + h.Wait() assert.Equal(t, http.StatusBadRequest, w.Code) assert.Equal(t, "Unknown webhook event\n", w.Body.String()) hook.Reset()