pkg/gitprovider/github.go (339 lines of code) (raw):

package gitprovider import ( "context" "fmt" "net/http" "strconv" "strings" "github.com/go-resty/resty/v2" ) type gitHubWebHook struct { ID int `json:"id"` Config struct { URL string `json:"url"` } `json:"config"` } type gitHubOrganization struct { Login string `json:"login"` } type GitHubClient struct { restyClient *resty.Client } const ( repoPathParam = "repo" ownerPathParam = "owner" ) // NewGitHubClient creates a new GitHub client. func NewGitHubClient(restyClient *resty.Client) *GitHubClient { restyClient.SetRetryCount(retryCount) restyClient.AddRetryCondition( func(response *resty.Response, err error) bool { return response.IsError() }, ) return &GitHubClient{restyClient: restyClient} } // CreateWebHook creates a new webhook for the given project. func (c *GitHubClient) CreateWebHook( ctx context.Context, githubURL, token, projectID, webHookSecret, webHookURL string, skipTLS bool, ) (*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } c.restyClient.HostURL = githubURL webHook := &gitHubWebHook{} insecure := 0 if skipTLS { insecure = 1 } resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetBody(map[string]interface{}{ "name": "web", "active": true, "events": []string{"pull_request", "push", "issue_comment"}, "config": map[string]string{ "url": webHookURL, "content_type": "json", "insecure_ssl": strconv.Itoa(insecure), "secret": webHookSecret, }, }). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, }). SetResult(webHook). Post("/repos/{owner}/{repo}/hooks") if err != nil { return nil, fmt.Errorf("failed to create GitHub web hook: %w", err) } if resp.IsError() { return nil, fmt.Errorf("failed to create GitHub web hook: %s", resp.String()) } return convertWebhook(webHook), nil } // CreateWebHookIfNotExists checks if a webhook with a given URL exists in the project. // If a webhook exists function returns it. If not, creates a new one. func (c *GitHubClient) CreateWebHookIfNotExists( ctx context.Context, githubURL, token, projectID, webHookSecret, webHookURL string, skipTLS bool, ) (*WebHook, error) { webHooks, err := c.GetWebHooks(ctx, githubURL, token, projectID) if err != nil { return nil, err } for _, webHook := range webHooks { if webHook.URL == webHookURL { return webHook, nil } } return c.CreateWebHook(ctx, githubURL, token, projectID, webHookSecret, webHookURL, skipTLS) } // GetWebHook gets a webhook by ID for the given project. func (c *GitHubClient) GetWebHook( ctx context.Context, githubURL, token, projectID string, webHookRef string, ) (*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } c.restyClient.HostURL = githubURL webHook := &gitHubWebHook{} resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, "hook-id": webHookRef, }). SetResult(webHook). Get("/repos/{owner}/{repo}/hooks/{hook-id}") if err != nil { return nil, fmt.Errorf("failed to get GitHub web hook: %w", err) } if resp.StatusCode() == http.StatusNotFound { return nil, fmt.Errorf("failed to get GitHub web hook: %w", ErrWebHookNotFound) } if resp.IsError() { return nil, fmt.Errorf("failed to get GitHub web hook: %s", resp.String()) } return convertWebhook(webHook), nil } // GetWebHooks gets a webhooks by the given project. func (c *GitHubClient) GetWebHooks( ctx context.Context, githubURL, token, projectID string, ) ([]*WebHook, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return nil, err } c.restyClient.HostURL = githubURL var gitHubWebHooks []*gitHubWebHook resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, }). SetResult(&gitHubWebHooks). Get("/repos/{owner}/{repo}/hooks") if err != nil { return nil, fmt.Errorf("failed to get GitHub web hooks: %w", err) } if resp.IsError() { return nil, fmt.Errorf("failed to get GitHub web hooks: %s", resp.String()) } webHooks := make([]*WebHook, len(gitHubWebHooks)) for i, webHook := range gitHubWebHooks { webHooks[i] = convertWebhook(webHook) } return webHooks, nil } // DeleteWebHook deletes webhook by ID for the given project. func (c *GitHubClient) DeleteWebHook( ctx context.Context, githubURL, token, projectID string, webHookRef string, ) error { owner, repo, err := parseProjectID(projectID) if err != nil { return err } c.restyClient.HostURL = githubURL resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, "hook-id": webHookRef, }). Delete("/repos/{owner}/{repo}/hooks/{hook-id}") if err != nil { return fmt.Errorf("failed to delete GitHub web hook: %w", err) } if resp.StatusCode() == http.StatusNotFound { return fmt.Errorf("failed to delete GitHub web hook: %w", ErrWebHookNotFound) } if resp.IsError() { return fmt.Errorf("failed to delete GitHub web hook: %s", resp.String()) } return nil } // CreateProject creates a new project. func (c *GitHubClient) CreateProject( ctx context.Context, githubURL, token, projectID string, ) error { c.restyClient.HostURL = githubURL path := "user/repos" owner, repo, err := parseProjectID(projectID) if err != nil { return err } isOrg, err := c.isOwnerOrg(ctx, githubURL, token, owner) if err != nil { return err } if isOrg { path = fmt.Sprintf("orgs/%v/repos", owner) } resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetBody(map[string]string{ "name": repo, }). Post(path) if err != nil { return fmt.Errorf("failed to create GitHub repository: %w", err) } if resp.IsError() { return fmt.Errorf("failed to create GitHub repository: %s", resp.String()) } return nil } // ProjectExists checks if the given project exists. func (c *GitHubClient) ProjectExists( ctx context.Context, githubURL, token, projectID string, ) (bool, error) { owner, repo, err := parseProjectID(projectID) if err != nil { return false, err } c.restyClient.HostURL = githubURL resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, }). Get("/repos/{owner}/{repo}") if err != nil { return false, fmt.Errorf("failed to get GitHub repository: %w", err) } if resp.IsError() { if resp.StatusCode() == http.StatusNotFound { return false, nil } return false, fmt.Errorf("failed to get GitHub repository: %s", resp.String()) } return true, nil } func (c *GitHubClient) SetDefaultBranch( ctx context.Context, githubURL, token, projectID, branch string, ) error { owner, repo, err := parseProjectID(projectID) if err != nil { return err } c.restyClient.HostURL = githubURL resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetPathParams(map[string]string{ ownerPathParam: owner, repoPathParam: repo, }). SetBody(map[string]string{ "default_branch": branch, }). Patch("/repos/{owner}/{repo}") if err != nil { return fmt.Errorf("failed to set GitHub default branch: %w", err) } if resp.IsError() { return fmt.Errorf("failed to set GitHub default branch: %s", resp.String()) } return nil } // isOwnerOrg checks if the given owner is an organization. func (c *GitHubClient) isOwnerOrg( ctx context.Context, githubURL, token string, owner string, ) (bool, error) { c.restyClient.HostURL = githubURL orgs := make([]gitHubOrganization, 0) resp, err := c.restyClient. R(). SetContext(ctx). SetAuthToken(token). SetQueryParam("per_page", "1000"). SetResult(&orgs). Get("/user/orgs") if err != nil { return false, fmt.Errorf("failed to get GitHub organizations: %w", err) } if resp.IsError() { return false, fmt.Errorf("failed to get GitHub organizations: %s", resp.String()) } for _, org := range orgs { if strings.EqualFold(org.Login, owner) { return true, nil } } return false, nil } func convertWebhook(githubHook *gitHubWebHook) *WebHook { if githubHook == nil { return nil } return &WebHook{ ID: strconv.Itoa(githubHook.ID), URL: githubHook.Config.URL, } }