sourcegraph/dev/codehost_testing/github_client.go
William Bezuidenhout f533eadf59
e2e test: define base tst framework for easier scenario setup (#56774)
* e2e tst: add actions to create github org (#56775)

* add basic actions to create org

* e2e tst: add actions to create github users (#56776)

* add basic actions to create and get users

* fix adminUser

* add unique id to user email

* e2e tst: add actions to create github teams (#56777)

* add basic actions to manage teams

* e2e tst: add actions to create repos (#56778)

---------

Co-authored-by: Petri-Johan Last <petri.last@sourcegraph.com>
Co-authored-by: Jean-Hadrien Chabran <jean-hadrien.chabran@sourcegraph.com>
2023-10-10 16:30:00 +02:00

311 lines
9.9 KiB
Go

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
}