github.com/google/go-github/v33@v33.0.0/github/repos_releases.go (about) 1 // Copyright 2013 The go-github AUTHORS. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 package github 7 8 import ( 9 "context" 10 "errors" 11 "fmt" 12 "io" 13 "mime" 14 "net/http" 15 "os" 16 "path/filepath" 17 "strings" 18 ) 19 20 // RepositoryRelease represents a GitHub release in a repository. 21 type RepositoryRelease struct { 22 TagName *string `json:"tag_name,omitempty"` 23 TargetCommitish *string `json:"target_commitish,omitempty"` 24 Name *string `json:"name,omitempty"` 25 Body *string `json:"body,omitempty"` 26 Draft *bool `json:"draft,omitempty"` 27 Prerelease *bool `json:"prerelease,omitempty"` 28 29 // The following fields are not used in CreateRelease or EditRelease: 30 ID *int64 `json:"id,omitempty"` 31 CreatedAt *Timestamp `json:"created_at,omitempty"` 32 PublishedAt *Timestamp `json:"published_at,omitempty"` 33 URL *string `json:"url,omitempty"` 34 HTMLURL *string `json:"html_url,omitempty"` 35 AssetsURL *string `json:"assets_url,omitempty"` 36 Assets []*ReleaseAsset `json:"assets,omitempty"` 37 UploadURL *string `json:"upload_url,omitempty"` 38 ZipballURL *string `json:"zipball_url,omitempty"` 39 TarballURL *string `json:"tarball_url,omitempty"` 40 Author *User `json:"author,omitempty"` 41 NodeID *string `json:"node_id,omitempty"` 42 } 43 44 func (r RepositoryRelease) String() string { 45 return Stringify(r) 46 } 47 48 // ReleaseAsset represents a GitHub release asset in a repository. 49 type ReleaseAsset struct { 50 ID *int64 `json:"id,omitempty"` 51 URL *string `json:"url,omitempty"` 52 Name *string `json:"name,omitempty"` 53 Label *string `json:"label,omitempty"` 54 State *string `json:"state,omitempty"` 55 ContentType *string `json:"content_type,omitempty"` 56 Size *int `json:"size,omitempty"` 57 DownloadCount *int `json:"download_count,omitempty"` 58 CreatedAt *Timestamp `json:"created_at,omitempty"` 59 UpdatedAt *Timestamp `json:"updated_at,omitempty"` 60 BrowserDownloadURL *string `json:"browser_download_url,omitempty"` 61 Uploader *User `json:"uploader,omitempty"` 62 NodeID *string `json:"node_id,omitempty"` 63 } 64 65 func (r ReleaseAsset) String() string { 66 return Stringify(r) 67 } 68 69 // ListReleases lists the releases for a repository. 70 // 71 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#list-releases 72 func (s *RepositoriesService) ListReleases(ctx context.Context, owner, repo string, opts *ListOptions) ([]*RepositoryRelease, *Response, error) { 73 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 74 u, err := addOptions(u, opts) 75 if err != nil { 76 return nil, nil, err 77 } 78 79 req, err := s.client.NewRequest("GET", u, nil) 80 if err != nil { 81 return nil, nil, err 82 } 83 84 var releases []*RepositoryRelease 85 resp, err := s.client.Do(ctx, req, &releases) 86 if err != nil { 87 return nil, resp, err 88 } 89 return releases, resp, nil 90 } 91 92 // GetRelease fetches a single release. 93 // 94 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release 95 func (s *RepositoriesService) GetRelease(ctx context.Context, owner, repo string, id int64) (*RepositoryRelease, *Response, error) { 96 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 97 return s.getSingleRelease(ctx, u) 98 } 99 100 // GetLatestRelease fetches the latest published release for the repository. 101 // 102 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-the-latest-release 103 func (s *RepositoriesService) GetLatestRelease(ctx context.Context, owner, repo string) (*RepositoryRelease, *Response, error) { 104 u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) 105 return s.getSingleRelease(ctx, u) 106 } 107 108 // GetReleaseByTag fetches a release with the specified tag. 109 // 110 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-by-tag-name 111 func (s *RepositoriesService) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*RepositoryRelease, *Response, error) { 112 u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) 113 return s.getSingleRelease(ctx, u) 114 } 115 116 func (s *RepositoriesService) getSingleRelease(ctx context.Context, url string) (*RepositoryRelease, *Response, error) { 117 req, err := s.client.NewRequest("GET", url, nil) 118 if err != nil { 119 return nil, nil, err 120 } 121 122 release := new(RepositoryRelease) 123 resp, err := s.client.Do(ctx, req, release) 124 if err != nil { 125 return nil, resp, err 126 } 127 return release, resp, nil 128 } 129 130 // repositoryReleaseRequest is a subset of RepositoryRelease and 131 // is used internally by CreateRelease and EditRelease to pass 132 // only the known fields for these endpoints. 133 // 134 // See https://github.com/google/go-github/issues/992 for more 135 // information. 136 type repositoryReleaseRequest struct { 137 TagName *string `json:"tag_name,omitempty"` 138 TargetCommitish *string `json:"target_commitish,omitempty"` 139 Name *string `json:"name,omitempty"` 140 Body *string `json:"body,omitempty"` 141 Draft *bool `json:"draft,omitempty"` 142 Prerelease *bool `json:"prerelease,omitempty"` 143 } 144 145 // CreateRelease adds a new release for a repository. 146 // 147 // Note that only a subset of the release fields are used. 148 // See RepositoryRelease for more information. 149 // 150 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#create-a-release 151 func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 152 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 153 154 releaseReq := &repositoryReleaseRequest{ 155 TagName: release.TagName, 156 TargetCommitish: release.TargetCommitish, 157 Name: release.Name, 158 Body: release.Body, 159 Draft: release.Draft, 160 Prerelease: release.Prerelease, 161 } 162 163 req, err := s.client.NewRequest("POST", u, releaseReq) 164 if err != nil { 165 return nil, nil, err 166 } 167 168 r := new(RepositoryRelease) 169 resp, err := s.client.Do(ctx, req, r) 170 if err != nil { 171 return nil, resp, err 172 } 173 return r, resp, nil 174 } 175 176 // EditRelease edits a repository release. 177 // 178 // Note that only a subset of the release fields are used. 179 // See RepositoryRelease for more information. 180 // 181 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#update-a-release 182 func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo string, id int64, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 183 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 184 185 releaseReq := &repositoryReleaseRequest{ 186 TagName: release.TagName, 187 TargetCommitish: release.TargetCommitish, 188 Name: release.Name, 189 Body: release.Body, 190 Draft: release.Draft, 191 Prerelease: release.Prerelease, 192 } 193 194 req, err := s.client.NewRequest("PATCH", u, releaseReq) 195 if err != nil { 196 return nil, nil, err 197 } 198 199 r := new(RepositoryRelease) 200 resp, err := s.client.Do(ctx, req, r) 201 if err != nil { 202 return nil, resp, err 203 } 204 return r, resp, nil 205 } 206 207 // DeleteRelease delete a single release from a repository. 208 // 209 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#delete-a-release 210 func (s *RepositoriesService) DeleteRelease(ctx context.Context, owner, repo string, id int64) (*Response, error) { 211 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 212 213 req, err := s.client.NewRequest("DELETE", u, nil) 214 if err != nil { 215 return nil, err 216 } 217 return s.client.Do(ctx, req, nil) 218 } 219 220 // ListReleaseAssets lists the release's assets. 221 // 222 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#list-release-assets 223 func (s *RepositoriesService) ListReleaseAssets(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*ReleaseAsset, *Response, error) { 224 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 225 u, err := addOptions(u, opts) 226 if err != nil { 227 return nil, nil, err 228 } 229 230 req, err := s.client.NewRequest("GET", u, nil) 231 if err != nil { 232 return nil, nil, err 233 } 234 235 var assets []*ReleaseAsset 236 resp, err := s.client.Do(ctx, req, &assets) 237 if err != nil { 238 return nil, resp, err 239 } 240 return assets, resp, nil 241 } 242 243 // GetReleaseAsset fetches a single release asset. 244 // 245 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-asset 246 func (s *RepositoriesService) GetReleaseAsset(ctx context.Context, owner, repo string, id int64) (*ReleaseAsset, *Response, error) { 247 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 248 249 req, err := s.client.NewRequest("GET", u, nil) 250 if err != nil { 251 return nil, nil, err 252 } 253 254 asset := new(ReleaseAsset) 255 resp, err := s.client.Do(ctx, req, asset) 256 if err != nil { 257 return nil, resp, err 258 } 259 return asset, resp, nil 260 } 261 262 // DownloadReleaseAsset downloads a release asset or returns a redirect URL. 263 // 264 // DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the 265 // specified release asset. It is the caller's responsibility to close the ReadCloser. 266 // If a redirect is returned, the redirect URL will be returned as a string instead 267 // of the io.ReadCloser. Exactly one of rc and redirectURL will be zero. 268 // 269 // followRedirectsClient can be passed to download the asset from a redirected 270 // location. Passing http.DefaultClient is recommended unless special circumstances 271 // exist, but it's possible to pass any http.Client. If nil is passed the 272 // redirectURL will be returned instead. 273 // 274 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#get-a-release-asset 275 func (s *RepositoriesService) DownloadReleaseAsset(ctx context.Context, owner, repo string, id int64, followRedirectsClient *http.Client) (rc io.ReadCloser, redirectURL string, err error) { 276 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 277 278 req, err := s.client.NewRequest("GET", u, nil) 279 if err != nil { 280 return nil, "", err 281 } 282 req.Header.Set("Accept", defaultMediaType) 283 284 s.client.clientMu.Lock() 285 defer s.client.clientMu.Unlock() 286 287 var loc string 288 saveRedirect := s.client.client.CheckRedirect 289 s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 290 loc = req.URL.String() 291 return errors.New("disable redirect") 292 } 293 defer func() { s.client.client.CheckRedirect = saveRedirect }() 294 295 req = withContext(ctx, req) 296 resp, err := s.client.client.Do(req) 297 if err != nil { 298 if !strings.Contains(err.Error(), "disable redirect") { 299 return nil, "", err 300 } 301 if followRedirectsClient != nil { 302 rc, err := s.downloadReleaseAssetFromURL(ctx, followRedirectsClient, loc) 303 return rc, "", err 304 } 305 return nil, loc, nil // Intentionally return no error with valid redirect URL. 306 } 307 308 if err := CheckResponse(resp); err != nil { 309 resp.Body.Close() 310 return nil, "", err 311 } 312 313 return resp.Body, "", nil 314 } 315 316 func (s *RepositoriesService) downloadReleaseAssetFromURL(ctx context.Context, followRedirectsClient *http.Client, url string) (rc io.ReadCloser, err error) { 317 req, err := http.NewRequest("GET", url, nil) 318 if err != nil { 319 return nil, err 320 } 321 req = withContext(ctx, req) 322 req.Header.Set("Accept", "*/*") 323 resp, err := followRedirectsClient.Do(req) 324 if err != nil { 325 return nil, err 326 } 327 if err := CheckResponse(resp); err != nil { 328 resp.Body.Close() 329 return nil, err 330 } 331 return resp.Body, nil 332 } 333 334 // EditReleaseAsset edits a repository release asset. 335 // 336 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#update-a-release-asset 337 func (s *RepositoriesService) EditReleaseAsset(ctx context.Context, owner, repo string, id int64, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { 338 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 339 340 req, err := s.client.NewRequest("PATCH", u, release) 341 if err != nil { 342 return nil, nil, err 343 } 344 345 asset := new(ReleaseAsset) 346 resp, err := s.client.Do(ctx, req, asset) 347 if err != nil { 348 return nil, resp, err 349 } 350 return asset, resp, nil 351 } 352 353 // DeleteReleaseAsset delete a single release asset from a repository. 354 // 355 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#delete-a-release-asset 356 func (s *RepositoriesService) DeleteReleaseAsset(ctx context.Context, owner, repo string, id int64) (*Response, error) { 357 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 358 359 req, err := s.client.NewRequest("DELETE", u, nil) 360 if err != nil { 361 return nil, err 362 } 363 return s.client.Do(ctx, req, nil) 364 } 365 366 // UploadReleaseAsset creates an asset by uploading a file into a release repository. 367 // To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. 368 // 369 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/repos/#upload-a-release-asset 370 func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, repo string, id int64, opts *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { 371 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 372 u, err := addOptions(u, opts) 373 if err != nil { 374 return nil, nil, err 375 } 376 377 stat, err := file.Stat() 378 if err != nil { 379 return nil, nil, err 380 } 381 if stat.IsDir() { 382 return nil, nil, errors.New("the asset to upload can't be a directory") 383 } 384 385 mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) 386 if opts.MediaType != "" { 387 mediaType = opts.MediaType 388 } 389 390 req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) 391 if err != nil { 392 return nil, nil, err 393 } 394 395 asset := new(ReleaseAsset) 396 resp, err := s.client.Do(ctx, req, asset) 397 if err != nil { 398 return nil, resp, err 399 } 400 return asset, resp, nil 401 }