github.com/creativeprojects/go-selfupdate@v1.2.0/gitlab_source.go (about) 1 package selfupdate 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "net/http" 8 "os" 9 10 "github.com/xanzy/go-gitlab" 11 ) 12 13 // GitLabConfig is an object to pass to NewGitLabSource 14 type GitLabConfig struct { 15 // APIToken represents GitLab API token. If it's not empty, it will be used for authentication for the API 16 APIToken string 17 // BaseURL is a base URL of your private GitLab instance 18 BaseURL string 19 } 20 21 // GitLabSource is used to load release information from GitLab 22 type GitLabSource struct { 23 api *gitlab.Client 24 token string 25 baseURL string 26 } 27 28 // NewGitLabSource creates a new GitLabSource from a config object. 29 // It initializes a GitLab API client. 30 // If you set your API token to the $GITLAB_TOKEN environment variable, the client will use it. 31 // You can pass an empty GitLabSource{} to use the default configuration 32 // The function will return an error if the GitLab Enterprise URLs in the config object cannot be parsed 33 func NewGitLabSource(config GitLabConfig) (*GitLabSource, error) { 34 token := config.APIToken 35 if token == "" { 36 // try the environment variable 37 token = os.Getenv("GITLAB_TOKEN") 38 } 39 option := make([]gitlab.ClientOptionFunc, 0, 1) 40 if config.BaseURL != "" { 41 option = append(option, gitlab.WithBaseURL(config.BaseURL)) 42 } 43 client, err := gitlab.NewClient(token, option...) 44 if err != nil { 45 return nil, fmt.Errorf("cannot create GitLab client: %w", err) 46 } 47 return &GitLabSource{ 48 api: client, 49 token: token, 50 baseURL: config.BaseURL, 51 }, nil 52 } 53 54 // ListReleases returns all available releases 55 func (s *GitLabSource) ListReleases(ctx context.Context, repository Repository) ([]SourceRelease, error) { 56 pid, err := repository.Get() 57 if err != nil { 58 return nil, err 59 } 60 61 rels, _, err := s.api.Releases.ListReleases(pid, nil, gitlab.WithContext(ctx)) 62 if err != nil { 63 return nil, fmt.Errorf("list releases: %w", err) 64 } 65 releases := make([]SourceRelease, len(rels)) 66 for i, rel := range rels { 67 releases[i] = NewGitLabRelease(rel) 68 } 69 return releases, nil 70 } 71 72 // DownloadReleaseAsset downloads an asset from a release. 73 // It returns an io.ReadCloser: it is your responsibility to Close it. 74 func (s *GitLabSource) DownloadReleaseAsset(ctx context.Context, rel *Release, assetID int64) (io.ReadCloser, error) { 75 if rel == nil { 76 return nil, ErrInvalidRelease 77 } 78 var downloadUrl string 79 if rel.AssetID == assetID { 80 downloadUrl = rel.AssetURL 81 } else if rel.ValidationAssetID == assetID { 82 downloadUrl = rel.ValidationAssetURL 83 } 84 if downloadUrl == "" { 85 return nil, fmt.Errorf("asset ID %d: %w", assetID, ErrAssetNotFound) 86 } 87 88 log.Printf("downloading %q", downloadUrl) 89 client := http.DefaultClient 90 req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, http.NoBody) 91 if err != nil { 92 log.Print(err) 93 return nil, err 94 } 95 96 if s.token != "" { 97 // verify request is from same domain not to leak token 98 ok, err := canUseTokenForDomain(s.baseURL, downloadUrl) 99 if err != nil { 100 return nil, err 101 } 102 if ok { 103 req.Header.Set("PRIVATE-TOKEN", s.token) 104 } 105 } 106 response, err := client.Do(req) 107 108 if err != nil { 109 log.Print(err) 110 return nil, err 111 } 112 113 return response.Body, nil 114 } 115 116 // Verify interface 117 var _ Source = &GitLabSource{}