-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathgithub_source.go
More file actions
155 lines (141 loc) · 5.2 KB
/
github_source.go
File metadata and controls
155 lines (141 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package selfupdate
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/google/go-github/v74/github"
"golang.org/x/oauth2"
)
// GitHubConfig is an object to pass to NewGitHubSource
type GitHubConfig struct {
// APIToken represents GitHub API token. If it's not empty, it will be used for authentication of GitHub API
APIToken string
// EnterpriseBaseURL is a base URL of GitHub API. If you want to use this library with GitHub Enterprise,
// please set "https://{your-organization-address}/api/v3/" to this field.
EnterpriseBaseURL string
// EnterpriseUploadURL is a URL to upload stuffs to GitHub Enterprise instance. This is often the same as an API base URL.
// So if this field is not set and EnterpriseBaseURL is set, EnterpriseBaseURL is also set to this field.
EnterpriseUploadURL string
// Deprecated: Context option is no longer used
Context context.Context
}
// GitHubSource is used to load release information from GitHub
type GitHubSource struct {
api *github.Client
}
// NewGitHubSource creates a new GitHubSource from a config object.
// It initializes a GitHub API client.
// If you set your API token to the $GITHUB_TOKEN environment variable, the client will use it.
// You can pass an empty GitHubSource{} to use the default configuration
// The function will return an error if the GitHub Enterprise URLs in the config object cannot be parsed
func NewGitHubSource(config GitHubConfig) (*GitHubSource, error) {
token := config.APIToken
if token == "" {
// try the environment variable
token = os.Getenv("GITHUB_TOKEN")
}
hc := newHTTPClient(token)
if config.EnterpriseBaseURL == "" {
// public (or private) repository on standard GitHub offering
client := github.NewClient(hc)
return &GitHubSource{
api: client,
}, nil
}
u := config.EnterpriseUploadURL
if u == "" {
u = config.EnterpriseBaseURL
}
client, err := github.NewEnterpriseClient(config.EnterpriseBaseURL, u, hc)
if err != nil {
return nil, fmt.Errorf("cannot parse GitHub enterprise URL: %w", err)
}
return &GitHubSource{
api: client,
}, nil
}
// ListReleases returns all available releases
func (s *GitHubSource) ListReleases(ctx context.Context, repository Repository) ([]SourceRelease, error) {
owner, repo, err := repository.GetSlug()
if err != nil {
return nil, err
}
rels, res, err := s.api.Repositories.ListReleases(ctx, owner, repo, nil)
if err != nil {
if res != nil && res.StatusCode == http.StatusNotFound {
// repository not found or release not found. It's not an error here.
log.Print("Repository or release not found")
return nil, nil
}
log.Printf("API returned an error response: %s", err)
return nil, err
}
releases := make([]SourceRelease, len(rels))
for i, rel := range rels {
releases[i] = NewGitHubRelease(rel)
}
return releases, nil
}
// DownloadReleaseAsset downloads an asset from a release.
// It returns an io.ReadCloser: it is your responsibility to Close it.
func (s *GitHubSource) DownloadReleaseAsset(ctx context.Context, rel *Release, assetID int64) (io.ReadCloser, error) {
if rel == nil {
return nil, ErrInvalidRelease
}
// Check if the AssetURL contains more than one "https://"
useGithubProxy := strings.Count(rel.AssetURL, "https://") > 1
// If the AssetURL contains more than 2 "https://", it means it's using a GitHub Proxy service.
// In this case, we should download the asset directly from the AssetURL instead of using the GitHub API.
// This is a workaround for the issue that the GitHub API does not support downloading assets from GitHub Proxy services.
if useGithubProxy {
// Determine download url based on asset id.
var downloadURL string
if rel.AssetID == assetID {
downloadURL = rel.AssetURL
} else if rel.ValidationAssetID == assetID {
downloadURL = rel.ValidationAssetURL
}
if downloadURL == "" {
return nil, fmt.Errorf("asset ID %d: %w", assetID, ErrAssetNotFound)
}
// Download the asset directly from the AssetURL
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, http.NoBody)
if err != nil {
return nil, fmt.Errorf("failed to create download request:%w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("download failed:%w", err)
}
// The caller is responsible for closing resp.Body
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
return nil, fmt.Errorf("download failed, status code:%d", resp.StatusCode)
}
return resp.Body, nil
}
// continue with the normal GitHub API download
owner, repo, err := rel.repository.GetSlug()
if err != nil {
return nil, err
}
// create a new http client so the GitHub library can download the redirected file (if any)
client := http.DefaultClient
rc, _, err := s.api.Repositories.DownloadReleaseAsset(ctx, owner, repo, assetID, client)
if err != nil {
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)
}
return rc, nil
}
func newHTTPClient(token string) *http.Client {
if token == "" {
return http.DefaultClient
}
src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
return oauth2.NewClient(context.Background(), src)
}
// Verify interface
var _ Source = &GitHubSource{}