Add Github wehbook management
Change-Id: I4b7a23a77838f345d65adf51877fab5978bd6055
diff --git a/server/git/git.go b/server/git/git.go
index 4cd7508..29cee16 100644
--- a/server/git/git.go
+++ b/server/git/git.go
@@ -69,6 +69,11 @@
RefreshAgentClone(agentName string) error
CleanupAgentClone(agentName string) error
CleanupAllClones() error
+
+ // Webhook operations (GitHub only)
+ ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error)
+ CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error)
+ UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error)
}
// Status represents the current state of the repository
@@ -679,6 +684,35 @@
return g.cloneManager.CleanupAllClones()
}
+// Webhook operations (GitHub only)
+
+// ListWebhooks lists existing webhooks for the repository
+func (g *Git) ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error) {
+ webhookProvider, ok := g.prProvider.(WebhookProvider)
+ if !ok {
+ return nil, &GitError{Command: "ListWebhooks", Output: "webhook operations not supported by current provider"}
+ }
+ return webhookProvider.ListWebhooks(ctx)
+}
+
+// CreateWebhook creates a new webhook for the repository
+func (g *Git) CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+ webhookProvider, ok := g.prProvider.(WebhookProvider)
+ if !ok {
+ return nil, &GitError{Command: "CreateWebhook", Output: "webhook operations not supported by current provider"}
+ }
+ return webhookProvider.CreateWebhook(ctx, webhookURL, secret)
+}
+
+// UpdateWebhook updates an existing webhook
+func (g *Git) UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error) {
+ webhookProvider, ok := g.prProvider.(WebhookProvider)
+ if !ok {
+ return nil, &GitError{Command: "UpdateWebhook", Output: "webhook operations not supported by current provider"}
+ }
+ return webhookProvider.UpdateWebhook(ctx, webhookID, webhookURL, secret)
+}
+
// Helper methods
func (g *Git) runCommand(cmd *exec.Cmd, command string) error {
@@ -982,3 +1016,10 @@
ClosePullRequest(ctx context.Context, id string) error
MergePullRequest(ctx context.Context, id string, options MergePullRequestOptions) error
}
+
+// WebhookProvider defines the interface for webhook operations
+type WebhookProvider interface {
+ ListWebhooks(ctx context.Context) ([]GitHubWebhookResponse, error)
+ CreateWebhook(ctx context.Context, webhookURL, secret string) (*GitHubWebhookResponse, error)
+ UpdateWebhook(ctx context.Context, webhookID int, webhookURL, secret string) (*GitHubWebhookResponse, error)
+}
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
+}