diff --git a/apiserver/controllers/controllers.go b/apiserver/controllers/controllers.go index 5ae3f571..8e42f320 100644 --- a/apiserver/controllers/controllers.go +++ b/apiserver/controllers/controllers.go @@ -61,7 +61,7 @@ type APIController struct { } func handleError(w http.ResponseWriter, err error) { - w.Header().Add("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") origErr := errors.Cause(err) apiErr := params.APIErrorResponse{ Details: origErr.Error(), @@ -214,8 +214,9 @@ func (a *APIController) NotFoundHandler(w http.ResponseWriter, r *http.Request) Details: "Resource not found", Error: "Not found", } - w.WriteHeader(http.StatusNotFound) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) if err := json.NewEncoder(w).Encode(apiErr); err != nil { log.Printf("failet to write response: %q", err) } diff --git a/apiserver/routers/routers.go b/apiserver/routers/routers.go index ef342f4a..161f9cee 100644 --- a/apiserver/routers/routers.go +++ b/apiserver/routers/routers.go @@ -82,7 +82,7 @@ func WithDebugServer(parentRouter *mux.Router) *mux.Router { return parentRouter } -func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddleware, initMiddleware, instanceMiddleware auth.Middleware) *mux.Router { +func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddleware, initMiddleware, instanceMiddleware auth.Middleware, manageWebhooks bool) *mux.Router { router := mux.NewRouter() logMiddleware := util.NewLoggingMiddleware(logWriter) router.Use(logMiddleware) @@ -204,16 +204,17 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl apiRouter.Handle("/repositories/", http.HandlerFunc(han.CreateRepoHandler)).Methods("POST", "OPTIONS") apiRouter.Handle("/repositories", http.HandlerFunc(han.CreateRepoHandler)).Methods("POST", "OPTIONS") - // Install Webhook - apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.InstallRepoWebhookHandler)).Methods("POST", "OPTIONS") - apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.InstallRepoWebhookHandler)).Methods("POST", "OPTIONS") - // Uninstall Webhook - apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") - apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") - // Get webhook info - apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") - apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") - + if manageWebhooks { + // Install Webhook + apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.InstallRepoWebhookHandler)).Methods("POST", "OPTIONS") + apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.InstallRepoWebhookHandler)).Methods("POST", "OPTIONS") + // Uninstall Webhook + apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") + apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.UninstallRepoWebhookHandler)).Methods("DELETE", "OPTIONS") + // Get webhook info + apiRouter.Handle("/repositories/{repoID}/webhook/", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") + apiRouter.Handle("/repositories/{repoID}/webhook", http.HandlerFunc(han.GetRepoWebhookInfoHandler)).Methods("GET", "OPTIONS") + } ///////////////////////////// // Organizations and pools // ///////////////////////////// @@ -253,16 +254,17 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl apiRouter.Handle("/organizations/", http.HandlerFunc(han.CreateOrgHandler)).Methods("POST", "OPTIONS") apiRouter.Handle("/organizations", http.HandlerFunc(han.CreateOrgHandler)).Methods("POST", "OPTIONS") - // Install Webhook - apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.InstallOrgWebhookHandler)).Methods("POST", "OPTIONS") - apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.InstallOrgWebhookHandler)).Methods("POST", "OPTIONS") - // Uninstall Webhook - apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") - apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") - // Get webhook info - apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") - apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") - + if manageWebhooks { + // Install Webhook + apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.InstallOrgWebhookHandler)).Methods("POST", "OPTIONS") + apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.InstallOrgWebhookHandler)).Methods("POST", "OPTIONS") + // Uninstall Webhook + apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") + apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.UninstallOrgWebhookHandler)).Methods("DELETE", "OPTIONS") + // Get webhook info + apiRouter.Handle("/organizations/{orgID}/webhook/", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") + apiRouter.Handle("/organizations/{orgID}/webhook", http.HandlerFunc(han.GetOrgWebhookInfoHandler)).Methods("GET", "OPTIONS") + } ///////////////////////////// // Enterprises and pools // ///////////////////////////// @@ -314,5 +316,8 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl // Websocket log writer apiRouter.Handle("/{ws:ws\\/?}", http.HandlerFunc(han.WSHandler)).Methods("GET") + + // NotFound handler + apiRouter.PathPrefix("/").HandlerFunc(han.NotFoundHandler).Methods("GET", "POST", "PUT", "DELETE", "OPTIONS") return router } diff --git a/cmd/garm/main.go b/cmd/garm/main.go index 04a45f0b..5399fee0 100644 --- a/cmd/garm/main.go +++ b/cmd/garm/main.go @@ -170,7 +170,7 @@ func main() { log.Fatal(err) } - router := routers.NewAPIRouter(controller, multiWriter, jwtMiddleware, initMiddleware, instanceMiddleware) + router := routers.NewAPIRouter(controller, multiWriter, jwtMiddleware, initMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement) if cfg.Metrics.Enable { log.Printf("registering prometheus metrics collectors") diff --git a/config/config.go b/config/config.go index f0751599..c2a12017 100644 --- a/config/config.go +++ b/config/config.go @@ -112,6 +112,8 @@ type Default struct { MetadataURL string `toml:"metadata_url" json:"metadata-url"` // WebhookURL is the URL that will be installed as a webhook target in github. WebhookURL string `toml:"webhook_url" json:"webhook-url"` + // EnableWebhookManagement enables the webhook management API. + EnableWebhookManagement bool `toml:"enable_webhook_management" json:"enable-webhook-management"` // LogFile is the location of the log file. LogFile string `toml:"log_file,omitempty" json:"log-file"` diff --git a/go.mod b/go.mod index 8f6fc253..adb8e1a1 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/go-openapi/errors v0.20.4 github.com/go-openapi/runtime v0.26.0 github.com/go-openapi/strfmt v0.21.7 + github.com/go-openapi/swag v0.22.4 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v53 v53.2.0 github.com/google/uuid v1.3.0 @@ -55,7 +56,6 @@ require ( github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/loads v0.21.2 // indirect github.com/go-openapi/spec v0.20.8 // indirect - github.com/go-openapi/swag v0.22.4 // indirect github.com/go-openapi/validate v0.22.1 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/golang/protobuf v1.5.3 // indirect diff --git a/runner/organizations.go b/runner/organizations.go index cb74888c..8e8c2a70 100644 --- a/runner/organizations.go +++ b/runner/organizations.go @@ -148,7 +148,7 @@ func (r *Runner) DeleteOrganization(ctx context.Context, orgID string, keepWebho return runnerErrors.NewBadRequestError("org has pools defined (%s)", strings.Join(poolIds, ", ")) } - if !keepWebhook { + if !keepWebhook && r.config.Default.EnableWebhookManagement { poolMgr, err := r.poolManagerCtrl.GetOrgPoolManager(org) if err != nil { return errors.Wrap(err, "fetching pool manager") diff --git a/runner/repositories.go b/runner/repositories.go index 512c21e4..54859c4e 100644 --- a/runner/repositories.go +++ b/runner/repositories.go @@ -147,7 +147,7 @@ func (r *Runner) DeleteRepository(ctx context.Context, repoID string, keepWebhoo return runnerErrors.NewBadRequestError("repo has pools defined (%s)", strings.Join(poolIds, ", ")) } - if !keepWebhook { + if !keepWebhook && r.config.Default.EnableWebhookManagement { poolMgr, err := r.poolManagerCtrl.GetRepoPoolManager(repo) if err != nil { return errors.Wrap(err, "fetching pool manager") diff --git a/testdata/config.toml b/testdata/config.toml index 75fa1376..6696dd25 100644 --- a/testdata/config.toml +++ b/testdata/config.toml @@ -17,8 +17,21 @@ callback_url = "https://garm.example.com/api/v1/callbacks" # highly encouraged. metadata_url = "https://garm.example.com/api/v1/metadata" +# This is the base URL where GARM will listen for webhook events from github. This +# URL can be directly configured in github to send events to. +# If GARM is allowed to manage webhooks, this URL will be used as a base to optionally +# create webhooks for repositories and organizations. To avoid clashes, the unique +# controller ID that gets generated when GARM is first installed, will be added as a suffix +# to this URL. +# +# For example, assuming that your GARM controller ID is "18225ce4-e3bd-43f0-9c85-7d7858bcc5b2" +# the webhook URL will be "https://garm.example.com/webhooks/18225ce4-e3bd-43f0-9c85-7d7858bcc5b2" webhook_url = "https://garm.example.com/webhooks" +# This option enables GARM to manage webhooks for repositories and organizations. Set this +# to false to disable the API routes that manage webhooks. +enable_webhook_management = true + # Uncomment this line if you'd like to log to a file instead of standard output. # log_file = "/tmp/runner-manager.log"