[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/PriNova/sourcegraph/main/dev/codehost_testing/github_client.go [Back]  [Original]

package codehost_testing

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"testing"

	"github.com/google/go-github/v55/github"

	"github.com/sourcegraph/sourcegraph/dev/codehost_testing/config"
	"github.com/sourcegraph/sourcegraph/lib/errors"
)

// GitHubClient provides methods for creating and retrieving resources with the configured GitHub codehost.
// The configured token is required to have the following scopes:
// - admin:enterprise
// - delete_repo
// - repo
// - site_admin
// - user
// - write:org
type GitHubClient struct {
	t   *testing.T
	cfg *config.GitHub
	c   *github.Client
}

// GetOrg returns the GitHub organization with the given name.
func (gh *GitHubClient) GetOrg(ctx context.Context, name string) (*github.Organization, error) {
	org, resp, err := gh.c.Organizations.Get(ctx, name)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to find org: %s - %s", name, respErrMsg)
	}
	return org, err
}

// CreateOrg creates a new GitHub organization with the given name using the Admin GitHub API.
func (gh *GitHubClient) CreateOrg(ctx context.Context, name string) (*github.Organization, error) {
	newOrg := github.Organization{
		Login: &name,
	}

	org, resp, err := gh.c.Admin.CreateOrg(ctx, &newOrg, gh.cfg.AdminUser)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to create org %q - %s", name, respErrMsg)
	}
	return org, err
}

// UpdateOrg updates an existing GitHub organization with the given Org values
func (gh *GitHubClient) UpdateOrg(ctx context.Context, org *github.Organization) (*github.Organization, error) {
	_, resp, err := gh.c.Organizations.Edit(ctx, org.GetLogin(), org)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to update actions permissions for org %q - %s", org.GetLogin(), respErrMsg)
	}
	return org, err
}

// CreateUser creates a new GitHub user with the given username using the GitHub Admin API
func (gh *GitHubClient) CreateUser(ctx context.Context, name, email string) (*github.User, error) {
	user, resp, err := gh.c.Admin.CreateUser(ctx, name, email)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to create user %q - %s", name, respErrMsg)
	}
	return user, nil
}

// GetUser returns the GitHub user with the given username.
func (gh *GitHubClient) GetUser(ctx context.Context, name string) (*github.User, error) {
	user, resp, err := gh.c.Users.Get(ctx, name)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to get user %q - %s", name, respErrMsg)
	}
	return user, nil
}

// DeleteUser deletes a GitHub user with the given username using the GitHub Admin API.
func (gh *GitHubClient) DeleteUser(ctx context.Context, username string) error {
	resp, err := gh.c.Admin.DeleteUser(ctx, username)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return errors.Newf("failed to delete user %q - %s", username, respErrMsg)
	}

	return nil
}

// GetTeam returns the GitHub team with the given name in the given Organization name.
func (gh *GitHubClient) GetTeam(ctx context.Context, org string, name string) (*github.Team, error) {
	team, resp, err := gh.c.Teams.GetTeamBySlug(ctx, org, name)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to get team %q - %s", name, respErrMsg)
	}

	return team, err
}

// CreateTeam creates a new GitHub team with the given name in the given Organization.
func (gh *GitHubClient) CreateTeam(ctx context.Context, org *github.Organization, name string) (*github.Team, error) {
	newTeam := github.NewTeam{
		Name:        name,
		Description: github.String("auto created team"),
		Privacy:     github.String("closed"),
	}
	team, resp, err := gh.c.Teams.CreateTeam(ctx, org.GetLogin(), newTeam)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to create team %q - %s", name, respErrMsg)
	}
	return team, err
}

// DeleteTeam deletes a GitHub team with the given name in the given Organization.
func (gh *GitHubClient) DeleteTeam(ctx context.Context, org *github.Organization, name string) error {
	resp, err := gh.c.Teams.DeleteTeamBySlug(ctx, org.GetLogin(), name)
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return errors.Newf("failed to delete team %q - %s", name, respErrMsg)
	}
	return err
}

// AssignTeamMembership adds team membership for a user in a team
func (gh *GitHubClient) AssignTeamMembership(ctx context.Context, org *github.Organization, team *github.Team, user *github.User) (*github.Team, error) {
	_, resp, err := gh.c.Teams.AddTeamMembershipBySlug(ctx, org.GetLogin(), team.GetSlug(), user.GetLogin(), &github.TeamAddTeamMembershipOptions{
		Role: "member",
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return team, nil
}

// GetRepo returns the GitHub repository with the given name in the given owner which should typically get the Organization name.
func (gh *GitHubClient) GetRepo(ctx context.Context, owner, repoName string) (*github.Repository, error) {
	repo, resp, err := gh.c.Repositories.Get(ctx, owner, repoName)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to get repo %q - %s", repoName, respErrMsg)
	}

	return repo, nil
}

// CreateRepo creates a new GitHub repository with the given name under the given org.
func (gh *GitHubClient) CreateRepo(ctx context.Context, org *github.Organization, repoName string, private bool) (*github.Repository, error) {
	repo, resp, err := gh.c.Repositories.Create(ctx, org.GetLogin(), &github.Repository{
		Name:    &repoName,
		Private: &private,
	})
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to create repo %q - %s", repoName, respErrMsg)
	}

	return repo, err
}

// ForkRepo forks a repository into an Organization. The repostiry will have the same name but the owner will be the given
// organization. Note that only the default branch is forked.
func (gh *GitHubClient) ForkRepo(ctx context.Context, org *github.Organization, owner, repoName string) error {
	_, resp, err := gh.c.Repositories.CreateFork(ctx, owner, repoName, &github.RepositoryCreateForkOptions{
		Organization:      org.GetLogin(),
		Name:              repoName,
		DefaultBranchOnly: true,
	})
	if err != nil {
		if resp.StatusCode == 202 {
			// expected - forking schedules a job on github side and the repo isn't immediately available
			return nil
		}
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return errors.Newf("failed to fork repo %q - %s", repoName, respErrMsg)
	}
	return nil
}

// UpdateRepo updates an existing GitHub repository with the given repo values
func (gh *GitHubClient) UpdateRepo(ctx context.Context, org *github.Organization, repo *github.Repository) (*github.Repository, error) {
	result, resp, err := gh.c.Repositories.Edit(ctx, org.GetLogin(), repo.GetName(), repo)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return nil, errors.Newf("failed to edit repository %q - %s", repo.GetName(), respErrMsg)
	}

	return result, nil
}

// DeleteTeam deletes a GitHub repo with the given name in the given Organization.
func (gh *GitHubClient) DeleteRepo(ctx context.Context, org *github.Organization, repo *github.Repository) error {
	resp, err := gh.c.Repositories.Delete(ctx, org.GetLogin(), repo.GetName())
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return errors.Newf("failed to edit repository %q - %s", repo.GetName(), respErrMsg)
	}

	if err != nil {
		return err
	}

	return nil
}

// UpdateTeamRepoPermissions updates the permissions of the given team for the given repository in the provided Organization.
func (gh *GitHubClient) UpdateTeamRepoPermissions(ctx context.Context, org *github.Organization, team *github.Team, repo *github.Repository) error {
	resp, err := gh.c.Teams.AddTeamRepoByID(ctx, org.GetID(), team.GetID(), org.GetLogin(), repo.GetName(), &github.TeamAddTeamRepoOptions{})
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 204 {
		respErrMsg := formatResponseErrMsg(gh.t, resp)
		return errors.Newf("failed to update repo %q permissions for team %q: %v", repo.GetName(), team.GetSlug(), respErrMsg)
	}
	return nil
}

func formatResponseErrMsg(t *testing.T, resp *github.Response) string {
	code := resp.StatusCode
	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Logf("failed to read response body: %v", err)
		return ""
	}
	return fmt.Sprintf("Status Code: %d\nBody: %s\n", code, string(raw))

}

// NewGitHubClient returns a new GitHub client from the given config. Note that the client sets InsecureSkipVerify to true
func NewGitHubClient(t *testing.T, cfg config.GitHub) (*GitHubClient, error) {
	t.Helper()
	httpClient := &http.Client{}

	gh, err := github.NewClient(httpClient).WithAuthToken(cfg.Token).WithEnterpriseURLs(cfg.URL, cfg.URL)

	if err != nil {
		return nil, err
	}

	c := GitHubClient{
		cfg: &cfg,
		c:   gh,
	}
	return &c, nil
}

Web Proxy Viewer  |  New URL  |  Original Page