pkg/git/providers/github/github.go (141 lines of code) (raw):

package github import ( "context" "errors" "fmt" "os" "regexp" "strings" goGithub "github.com/google/go-github/v35/github" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/git" "github.com/aws/eks-anywhere/pkg/logger" ) const ( GitProviderName = "github" EksaGithubTokenEnv = "EKSA_GITHUB_TOKEN" GithubTokenEnv = "GITHUB_TOKEN" githubUrlTemplate = "https://github.com/%v/%v.git" patRegex = "^ghp_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$" repoPermissions = "repo" ) type githubProvider struct { githubProviderClient GithubClient config *v1alpha1.GithubProviderConfig auth git.TokenAuth } type Options struct { Repository string Owner string Personal bool } // GithubClient represents the attributes that the Github provider requires of a library to directly connect to and interact with the Github API. type GithubClient interface { GetRepo(ctx context.Context, opts git.GetRepoOpts) (repo *git.Repository, err error) CreateRepo(ctx context.Context, opts git.CreateRepoOpts) (repo *git.Repository, err error) AddDeployKeyToRepo(ctx context.Context, opts git.AddDeployKeyOpts) error AuthenticatedUser(ctx context.Context) (*goGithub.User, error) Organization(ctx context.Context, org string) (*goGithub.Organization, error) GetAccessTokenPermissions(accessToken string) (string, error) CheckAccessTokenPermissions(checkPATPermission string, allPermissionScopes string) error PathExists(ctx context.Context, owner, repo, branch, path string) (bool, error) DeleteRepo(ctx context.Context, opts git.DeleteRepoOpts) error } func New(githubProviderClient GithubClient, config *v1alpha1.GithubProviderConfig, auth git.TokenAuth) (*githubProvider, error) { return &githubProvider{ githubProviderClient: githubProviderClient, config: config, auth: auth, }, nil } // CreateRepo creates an empty Github Repository. The repository must be initialized locally or // file must be added to it via the github api before it can be successfully cloned. func (g *githubProvider) CreateRepo(ctx context.Context, opts git.CreateRepoOpts) (repository *git.Repository, err error) { return g.githubProviderClient.CreateRepo(ctx, opts) } // GetRepo describes a remote repository, return the repo name if it exists. // If the repo does not exist, a nil repo is returned. func (g *githubProvider) GetRepo(ctx context.Context) (*git.Repository, error) { r := g.config.Repository o := g.config.Owner logger.V(3).Info("Describing Github repository", "name", r, "owner", o) opts := git.GetRepoOpts{Owner: o, Repository: r} repo, err := g.githubProviderClient.GetRepo(ctx, opts) if err != nil { var e *git.RepositoryDoesNotExistError if errors.As(err, &e) { return nil, nil } return nil, fmt.Errorf("unexpected error when describing repository %s: %w", r, err) } return repo, err } func (g *githubProvider) AddDeployKeyToRepo(ctx context.Context, opts git.AddDeployKeyOpts) error { return g.githubProviderClient.AddDeployKeyToRepo(ctx, opts) } // validates the github setup and access. func (g *githubProvider) Validate(ctx context.Context) error { user, err := g.githubProviderClient.AuthenticatedUser(ctx) if err != nil { return err } accessToken := g.auth.Token allPermissions, err := g.githubProviderClient.GetAccessTokenPermissions(accessToken) if err != nil { return err } err = g.githubProviderClient.CheckAccessTokenPermissions(repoPermissions, allPermissions) if err != nil { return err } logger.MarkPass("Github personal access token has the required repo permissions") if g.config.Personal { if !strings.EqualFold(g.config.Owner, *user.Login) { return fmt.Errorf("the authenticated Github.com user and owner %s specified in the EKS-A gitops spec don't match; confirm access token owner is %s", g.config.Owner, g.config.Owner) } return nil } org, err := g.githubProviderClient.Organization(ctx, g.config.Owner) if err != nil { return fmt.Errorf("the authenticated github user doesn't have proper access to github organization %s, %v", g.config.Owner, err) } if org == nil { // for now only checks if user belongs to the org return fmt.Errorf("the authenticated github user doesn't have proper access to github organization %s", g.config.Owner) } return nil } func validateGithubAccessToken() error { r := regexp.MustCompile(patRegex) logger.V(4).Info("Checking validity of Github Access Token environment variable", "env var", EksaGithubTokenEnv) val, ok := os.LookupEnv(EksaGithubTokenEnv) if !ok { return fmt.Errorf("github access token environment variable %s is invalid; could not get var from environment", EksaGithubTokenEnv) } if !r.MatchString(val) { return fmt.Errorf("github access token environment variable %s is invalid; must match format %s", EksaGithubTokenEnv, patRegex) } return nil } func GetGithubAccessTokenFromEnv() (string, error) { err := validateGithubAccessToken() if err != nil { return "", err } env := make(map[string]string) if val, ok := os.LookupEnv(EksaGithubTokenEnv); ok && len(val) > 0 { env[GithubTokenEnv] = val if err := os.Setenv(GithubTokenEnv, val); err != nil { return "", fmt.Errorf("unable to set %s: %v", GithubTokenEnv, err) } } return env[GithubTokenEnv], nil } func (g *githubProvider) PathExists(ctx context.Context, owner, repo, branch, path string) (bool, error) { return g.githubProviderClient.PathExists(ctx, owner, repo, branch, path) } func (g *githubProvider) DeleteRepo(ctx context.Context, opts git.DeleteRepoOpts) error { return g.githubProviderClient.DeleteRepo(ctx, opts) } type GitProviderNotFoundError struct { Provider string } func (e *GitProviderNotFoundError) Error() string { return fmt.Sprintf("git provider %s not found", e.Provider) } func RepoUrl(owner string, repo string) string { return fmt.Sprintf(githubUrlTemplate, owner, repo) }