<commit_before>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package github

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/go-github/v33/github"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/oauth2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/github/internal"
	"k8s.io/release/pkg/util"
)

const (
	// TokenEnvKey is the default GitHub token environemt variable key
	TokenEnvKey = "GITHUB_TOKEN"
	// GitHubURL Prefix for github URLs
	GitHubURL = "https://github.com/"
)

// GitHub is a wrapper around GitHub related functionality
type GitHub struct {
	client Client
}

type githubClient struct {
	*github.Client
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Client
type Client interface {
	GetCommit(
		context.Context, string, string, string,
	) (*github.Commit, *github.Response, error)

	GetPullRequest(
		context.Context, string, string, int,
	) (*github.PullRequest, *github.Response, error)

	GetIssue(
		context.Context, string, string, int,
	) (*github.Issue, *github.Response, error)

	GetRepoCommit(
		context.Context, string, string, string,
	) (*github.RepositoryCommit, *github.Response, error)

	ListCommits(
		context.Context, string, string, *github.CommitsListOptions,
	) ([]*github.RepositoryCommit, *github.Response, error)

	ListPullRequestsWithCommit(
		context.Context, string, string, string, *github.PullRequestListOptions,
	) ([]*github.PullRequest, *github.Response, error)

	ListReleases(
		context.Context, string, string, *github.ListOptions,
	) ([]*github.RepositoryRelease, *github.Response, error)

	GetReleaseByTag(
		context.Context, string, string, string,
	) (*github.RepositoryRelease, *github.Response, error)

	DownloadReleaseAsset(
		context.Context, string, string, int64,
	) (io.ReadCloser, string, error)

	ListTags(
		context.Context, string, string, *github.ListOptions,
	) ([]*github.RepositoryTag, *github.Response, error)

	ListBranches(
		context.Context, string, string, *github.BranchListOptions,
	) ([]*github.Branch, *github.Response, error)

	CreatePullRequest(
		context.Context, string, string, string, string, string, string,
	) (*github.PullRequest, error)

	GetRepository(
		context.Context, string, string,
	) (*github.Repository, *github.Response, error)

	UpdateReleasePage(
		context.Context, string, string, int64, *github.RepositoryRelease,
	) (*github.RepositoryRelease, error)

	UploadReleaseAsset(
		context.Context, string, string, int64, *github.UploadOptions, *os.File,
	) (*github.ReleaseAsset, error)

	DeleteReleaseAsset(
		context.Context, string, string, int64,
	) error

	ListReleaseAssets(
		context.Context, string, string, int64,
	) ([]*github.ReleaseAsset, error)

	CreateComment(
		context.Context, string, string, int, string,
	) (*github.IssueComment, *github.Response, error)
}

// New creates a new default GitHub client. Tokens set via the $GITHUB_TOKEN
// environment variable will result in an authenticated client.
// If the $GITHUB_TOKEN is not set, then the client will do unauthenticated
// GitHub requests.
func New() *GitHub {
	ctx := context.Background()
	token := util.EnvDefault(TokenEnvKey, "")
	client := http.DefaultClient
	state := "unauthenticated"
	if token != "" {
		state = strings.TrimPrefix(state, "un")
		client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
	}
	logrus.Debugf("Using %s GitHub client", state)
	return &GitHub{&githubClient{github.NewClient(client)}}
}

// NewWithToken can be used to specify a GITHUB_TOKEN before retrieving the
// client to enforce authenticated GitHub requests
func NewWithToken(token string) (*GitHub, error) {
	if err := os.Setenv(TokenEnvKey, token); err != nil {
		return nil, errors.Wrapf(err, "unable to export %s", TokenEnvKey)
	}
	return New(), nil
}

func NewEnterprise(baseURL, uploadURL string) (*GitHub, error) {
	ctx := context.Background()
	token := util.EnvDefault(TokenEnvKey, "")
	client := http.DefaultClient
	state := "unauthenticated"
	if token != "" {
		state = strings.TrimPrefix(state, "un")
		client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
	}
	logrus.Debugf("Using %s Enterprise GitHub client", state)
	ghclient, err := github.NewEnterpriseClient(baseURL, uploadURL, client)
	if err != nil {
		return nil, fmt.Errorf("failed to new github client: %s", err)
	}
	return &GitHub{&githubClient{ghclient}}, nil
}

func NewEnterpriseWithToken(baseURL, uploadURL, token string) (*GitHub, error) {
	if err := os.Setenv(TokenEnvKey, token); err != nil {
		return nil, errors.Wrapf(err, "unable to export %s", TokenEnvKey)
	}
	return NewEnterprise(baseURL, uploadURL)
}

func (g *githubClient) GetCommit(
	ctx context.Context, owner, repo, sha string,
) (*github.Commit, *github.Response, error) {
	for shouldRetry := internal.DefaultGithubErrChecker(); ; {
		commit, resp, err := g.Git.GetCommit(ctx, owner, repo, sha)
		if !shouldRetry(err) {<commit_msg>pkg/github will no longer set GITHUB_TOKEN variable<commit_after>/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package github

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/go-github/v33/github"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/oauth2"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/github/internal"
	"k8s.io/release/pkg/util"
)

const (
	// TokenEnvKey is the default GitHub token environemt variable key
	TokenEnvKey = "GITHUB_TOKEN"
	// GitHubURL Prefix for github URLs
	GitHubURL = "https://github.com/"
)

// GitHub is a wrapper around GitHub related functionality
type GitHub struct {
	client Client
}

type githubClient struct {
	*github.Client
}

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
//counterfeiter:generate . Client
type Client interface {
	GetCommit(
		context.Context, string, string, string,
	) (*github.Commit, *github.Response, error)

	GetPullRequest(
		context.Context, string, string, int,
	) (*github.PullRequest, *github.Response, error)

	GetIssue(
		context.Context, string, string, int,
	) (*github.Issue, *github.Response, error)

	GetRepoCommit(
		context.Context, string, string, string,
	) (*github.RepositoryCommit, *github.Response, error)

	ListCommits(
		context.Context, string, string, *github.CommitsListOptions,
	) ([]*github.RepositoryCommit, *github.Response, error)

	ListPullRequestsWithCommit(
		context.Context, string, string, string, *github.PullRequestListOptions,
	) ([]*github.PullRequest, *github.Response, error)

	ListReleases(
		context.Context, string, string, *github.ListOptions,
	) ([]*github.RepositoryRelease, *github.Response, error)

	GetReleaseByTag(
		context.Context, string, string, string,
	) (*github.RepositoryRelease, *github.Response, error)

	DownloadReleaseAsset(
		context.Context, string, string, int64,
	) (io.ReadCloser, string, error)

	ListTags(
		context.Context, string, string, *github.ListOptions,
	) ([]*github.RepositoryTag, *github.Response, error)

	ListBranches(
		context.Context, string, string, *github.BranchListOptions,
	) ([]*github.Branch, *github.Response, error)

	CreatePullRequest(
		context.Context, string, string, string, string, string, string,
	) (*github.PullRequest, error)

	GetRepository(
		context.Context, string, string,
	) (*github.Repository, *github.Response, error)

	UpdateReleasePage(
		context.Context, string, string, int64, *github.RepositoryRelease,
	) (*github.RepositoryRelease, error)

	UploadReleaseAsset(
		context.Context, string, string, int64, *github.UploadOptions, *os.File,
	) (*github.ReleaseAsset, error)

	DeleteReleaseAsset(
		context.Context, string, string, int64,
	) error

	ListReleaseAssets(
		context.Context, string, string, int64,
	) ([]*github.ReleaseAsset, error)

	CreateComment(
		context.Context, string, string, int, string,
	) (*github.IssueComment, *github.Response, error)
}

// TODO: we should clean up the functions listed below and agree on the same
// return type (with or without error):
// - New
// - NewWithToken
// - NewEnterprise
// - NewEnterpriseWithToken

// New creates a new default GitHub client. Tokens set via the $GITHUB_TOKEN
// environment variable will result in an authenticated client.
// If the $GITHUB_TOKEN is not set, then the client will do unauthenticated
// GitHub requests.
func New() *GitHub {
	token := util.EnvDefault(TokenEnvKey, "")
	client, _ := NewWithToken(token) // nolint: errcheck
	return client
}

// NewWithToken can be used to specify a GitHub token through parameters.
// Empty string will result in unauthenticated client, which makes
// unauthenticated requests.
func NewWithToken(token string) (*GitHub, error) {
	ctx := context.Background()
	client := http.DefaultClient
	state := "unauthenticated"
	if token != "" {
		state = strings.TrimPrefix(state, "un")
		client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
	}
	logrus.Debugf("Using %s GitHub client", state)
	return &GitHub{&githubClient{github.NewClient(client)}}, nil
}

func NewEnterprise(baseURL, uploadURL string) (*GitHub, error) {
	token := util.EnvDefault(TokenEnvKey, "")
	return NewEnterpriseWithToken(baseURL, uploadURL, token)
}

func NewEnterpriseWithToken(baseURL, uploadURL, token string) (*GitHub, error) {
	ctx := context.Background()
	client := http.DefaultClient
	state := "unauthenticated"
	if token != "" {
		state = strings.TrimPrefix(state, "un")
		client = oauth2.NewClient(ctx, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
	}
	logrus.Debugf("Using %s Enterprise GitHub client", state)
	ghclient, err := github.NewEnterpriseClient(baseURL, uploadURL, client)
	if err != nil {
		return nil, fmt.Errorf("failed to new github client: %s", err)
	}
	return &GitHub{&githubClient{ghclient}}, nil
}

func (g *githubClient) GetCommit(
	ctx context.Context, owner, repo, sha string,
) (*github.Commit, *github.Response, error) {
	for shouldRetry := internal.DefaultGithubErrChecker(); ; {
		commit, resp, err := g.Git.GetCommit(ctx, owner, repo, sha)
		if !shouldRetry(err) {
{'fraction_non_alphanumeric': 0.12315489951982928, 'fraction_numerical': 0.005513071314245065, 'mean_word_length': 4.931962025316456, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5132987', 'n_tokens_mistral': 3752, 'n_tokens_neox': 3552, 'n_words': 996}