github.com/creativeprojects/go-selfupdate@v1.2.0/github_source.go (about)

     1  package selfupdate
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  
    10  	"github.com/google/go-github/v30/github"
    11  	"golang.org/x/oauth2"
    12  )
    13  
    14  // GitHubConfig is an object to pass to NewGitHubSource
    15  type GitHubConfig struct {
    16  	// APIToken represents GitHub API token. If it's not empty, it will be used for authentication of GitHub API
    17  	APIToken string
    18  	// EnterpriseBaseURL is a base URL of GitHub API. If you want to use this library with GitHub Enterprise,
    19  	// please set "https://{your-organization-address}/api/v3/" to this field.
    20  	EnterpriseBaseURL string
    21  	// EnterpriseUploadURL is a URL to upload stuffs to GitHub Enterprise instance. This is often the same as an API base URL.
    22  	// So if this field is not set and EnterpriseBaseURL is set, EnterpriseBaseURL is also set to this field.
    23  	EnterpriseUploadURL string
    24  	// Deprecated: Context option is no longer used
    25  	Context context.Context
    26  }
    27  
    28  // GitHubSource is used to load release information from GitHub
    29  type GitHubSource struct {
    30  	api *github.Client
    31  }
    32  
    33  // NewGitHubSource creates a new GitHubSource from a config object.
    34  // It initializes a GitHub API client.
    35  // If you set your API token to the $GITHUB_TOKEN environment variable, the client will use it.
    36  // You can pass an empty GitHubSource{} to use the default configuration
    37  // The function will return an error if the GitHub Enterprise URLs in the config object cannot be parsed
    38  func NewGitHubSource(config GitHubConfig) (*GitHubSource, error) {
    39  	token := config.APIToken
    40  	if token == "" {
    41  		// try the environment variable
    42  		token = os.Getenv("GITHUB_TOKEN")
    43  	}
    44  	hc := newHTTPClient(token)
    45  
    46  	if config.EnterpriseBaseURL == "" {
    47  		// public (or private) repository on standard GitHub offering
    48  		client := github.NewClient(hc)
    49  		return &GitHubSource{
    50  			api: client,
    51  		}, nil
    52  	}
    53  
    54  	u := config.EnterpriseUploadURL
    55  	if u == "" {
    56  		u = config.EnterpriseBaseURL
    57  	}
    58  	client, err := github.NewEnterpriseClient(config.EnterpriseBaseURL, u, hc)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("cannot parse GitHub enterprise URL: %w", err)
    61  	}
    62  	return &GitHubSource{
    63  		api: client,
    64  	}, nil
    65  }
    66  
    67  // ListReleases returns all available releases
    68  func (s *GitHubSource) ListReleases(ctx context.Context, repository Repository) ([]SourceRelease, error) {
    69  	owner, repo, err := repository.GetSlug()
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	rels, res, err := s.api.Repositories.ListReleases(ctx, owner, repo, nil)
    74  	if err != nil {
    75  		if res != nil && res.StatusCode == 404 {
    76  			// 404 means repository not found or release not found. It's not an error here.
    77  			log.Print("Repository or release not found")
    78  			return nil, nil
    79  		}
    80  		log.Printf("API returned an error response: %s", err)
    81  		return nil, err
    82  	}
    83  	releases := make([]SourceRelease, len(rels))
    84  	for i, rel := range rels {
    85  		releases[i] = NewGitHubRelease(rel)
    86  	}
    87  	return releases, nil
    88  }
    89  
    90  // DownloadReleaseAsset downloads an asset from a release.
    91  // It returns an io.ReadCloser: it is your responsibility to Close it.
    92  func (s *GitHubSource) DownloadReleaseAsset(ctx context.Context, rel *Release, assetID int64) (io.ReadCloser, error) {
    93  	if rel == nil {
    94  		return nil, ErrInvalidRelease
    95  	}
    96  	owner, repo, err := rel.repository.GetSlug()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	// create a new http client so the GitHub library can download the redirected file (if any)
   101  	client := http.DefaultClient
   102  	rc, _, err := s.api.Repositories.DownloadReleaseAsset(ctx, owner, repo, assetID, client)
   103  	if err != nil {
   104  		return nil, fmt.Errorf("failed to call GitHub Releases API for getting the asset ID %d on repository '%s/%s': %w", assetID, owner, repo, err)
   105  	}
   106  	return rc, nil
   107  }
   108  
   109  func newHTTPClient(token string) *http.Client {
   110  	if token == "" {
   111  		return http.DefaultClient
   112  	}
   113  	src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
   114  	return oauth2.NewClient(context.Background(), src)
   115  }
   116  
   117  // Verify interface
   118  var _ Source = &GitHubSource{}