Add Github wehbook management
Change-Id: I4b7a23a77838f345d65adf51877fab5978bd6055
diff --git a/server/git/github.go b/server/git/github.go
index 1d37b1d..a73c4c6 100644
--- a/server/git/github.go
+++ b/server/git/github.go
@@ -112,6 +112,29 @@
MergeMethod string `json:"merge_method,omitempty"`
}
+// GitHub webhook API types
+type githubWebhookRequest struct {
+ Name string `json:"name"`
+ Active bool `json:"active"`
+ Events []string `json:"events"`
+ Config githubWebhookConfig `json:"config"`
+}
+
+type githubWebhookConfig struct {
+ URL string `json:"url"`
+ ContentType string `json:"content_type"`
+ Secret string `json:"secret"`
+}
+
+type GitHubWebhookResponse struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Active bool `json:"active"`
+ Events []string `json:"events"`
+ Config githubWebhookConfig `json:"config"`
+ URL string `json:"url"`
+}
+
// CreatePullRequest creates a new pull request on GitHub
func (g *GitHubPullRequestProvider) CreatePullRequest(ctx context.Context, options PullRequestOptions) (*PullRequest, error) {
reqBody := githubCreatePRRequest{
@@ -415,3 +438,140 @@
URL: fmt.Sprintf("https://github.com/%s/%s/pull/%d", g.owner, g.repo, githubPR.Number),
}
}
+
+// ListWebhooks lists existing webhooks for the repository
+func (g *GitHubPullRequestProvider) ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error) {
+ url := fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)
+ req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("Authorization", "token "+g.config.Token)
+ req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+ resp, err := g.config.HTTPClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ var errorBody bytes.Buffer
+ _, _ = errorBody.ReadFrom(resp.Body)
+ return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+ }
+
+ var webhooks []GitHubWebhookResponse
+ if err := json.NewDecoder(resp.Body).Decode(&webhooks); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return webhooks, nil
+}
+
+// CreateWebhook creates a new webhook for the repository
+func (g *GitHubPullRequestProvider) CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+ reqBody := githubWebhookRequest{
+ Name: "web",
+ Active: true,
+ Events: []string{"pull_request"},
+ Config: githubWebhookConfig{
+ URL: webhookURL,
+ ContentType: "json",
+ Secret: secret,
+ },
+ }
+
+ jsonBody, err := json.Marshal(reqBody)
+ if err != nil {
+ return nil, fmt.Errorf("failed to marshal request body: %w", err)
+ }
+
+ g.logger.Info("Creating GitHub webhook",
+ slog.String("url", fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)),
+ slog.String("webhook_url", webhookURL),
+ slog.Any("events", reqBody.Events))
+
+ url := fmt.Sprintf("%s/repos/%s/%s/hooks", g.config.BaseURL, g.owner, g.repo)
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("Authorization", "token "+g.config.Token)
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+ resp, err := g.config.HTTPClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusCreated {
+ var errorBody bytes.Buffer
+ _, _ = errorBody.ReadFrom(resp.Body)
+ return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+ }
+
+ var webhook GitHubWebhookResponse
+ if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &webhook, nil
+}
+
+// UpdateWebhook updates an existing webhook
+func (g *GitHubPullRequestProvider) UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+ reqBody := githubWebhookRequest{
+ Name: "web",
+ Active: true,
+ Events: []string{"pull_request"},
+ Config: githubWebhookConfig{
+ URL: webhookURL,
+ ContentType: "json",
+ Secret: secret,
+ },
+ }
+
+ jsonBody, err := json.Marshal(reqBody)
+ if err != nil {
+ return nil, fmt.Errorf("failed to marshal request body: %w", err)
+ }
+
+ g.logger.Info("Updating GitHub webhook",
+ slog.Int("webhook_id", webhookID),
+ slog.String("webhook_url", webhookURL),
+ slog.Any("events", reqBody.Events))
+
+ url := fmt.Sprintf("%s/repos/%s/%s/hooks/%d", g.config.BaseURL, g.owner, g.repo, webhookID)
+ req, err := http.NewRequestWithContext(ctx, "PATCH", url, bytes.NewBuffer(jsonBody))
+ if err != nil {
+ return nil, fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("Authorization", "token "+g.config.Token)
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Accept", "application/vnd.github.v3+json")
+
+ resp, err := g.config.HTTPClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to make request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ var errorBody bytes.Buffer
+ _, _ = errorBody.ReadFrom(resp.Body)
+ return nil, fmt.Errorf("GitHub API error: %d - %s", resp.StatusCode, errorBody.String())
+ }
+
+ var webhook GitHubWebhookResponse
+ if err := json.NewDecoder(resp.Body).Decode(&webhook); err != nil {
+ return nil, fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ return &webhook, nil
+}