github.com/google/go-github/v74@v74.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 // MakeLatest can be one of: "true", "false", or "legacy". 29 MakeLatest *string `json:"make_latest,omitempty"` 30 DiscussionCategoryName *string `json:"discussion_category_name,omitempty"` 31 32 // The following fields are not used in EditRelease: 33 GenerateReleaseNotes *bool `json:"generate_release_notes,omitempty"` 34 35 // The following fields are not used in CreateRelease or EditRelease: 36 ID *int64 `json:"id,omitempty"` 37 CreatedAt *Timestamp `json:"created_at,omitempty"` 38 PublishedAt *Timestamp `json:"published_at,omitempty"` 39 URL *string `json:"url,omitempty"` 40 HTMLURL *string `json:"html_url,omitempty"` 41 AssetsURL *string `json:"assets_url,omitempty"` 42 Assets []*ReleaseAsset `json:"assets,omitempty"` 43 UploadURL *string `json:"upload_url,omitempty"` 44 ZipballURL *string `json:"zipball_url,omitempty"` 45 TarballURL *string `json:"tarball_url,omitempty"` 46 Author *User `json:"author,omitempty"` 47 NodeID *string `json:"node_id,omitempty"` 48 } 49 50 func (r RepositoryRelease) String() string { 51 return Stringify(r) 52 } 53 54 // RepositoryReleaseNotes represents a GitHub-generated release notes. 55 type RepositoryReleaseNotes struct { 56 Name string `json:"name"` 57 Body string `json:"body"` 58 } 59 60 // GenerateNotesOptions represents the options to generate release notes. 61 type GenerateNotesOptions struct { 62 TagName string `json:"tag_name"` 63 PreviousTagName *string `json:"previous_tag_name,omitempty"` 64 TargetCommitish *string `json:"target_commitish,omitempty"` 65 } 66 67 // ReleaseAsset represents a GitHub release asset in a repository. 68 type ReleaseAsset struct { 69 ID *int64 `json:"id,omitempty"` 70 URL *string `json:"url,omitempty"` 71 Name *string `json:"name,omitempty"` 72 Label *string `json:"label,omitempty"` 73 State *string `json:"state,omitempty"` 74 ContentType *string `json:"content_type,omitempty"` 75 Size *int `json:"size,omitempty"` 76 DownloadCount *int `json:"download_count,omitempty"` 77 CreatedAt *Timestamp `json:"created_at,omitempty"` 78 UpdatedAt *Timestamp `json:"updated_at,omitempty"` 79 BrowserDownloadURL *string `json:"browser_download_url,omitempty"` 80 Uploader *User `json:"uploader,omitempty"` 81 NodeID *string `json:"node_id,omitempty"` 82 Digest *string `json:"digest,omitempty"` 83 } 84 85 func (r ReleaseAsset) String() string { 86 return Stringify(r) 87 } 88 89 // ListReleases lists the releases for a repository. 90 // 91 // GitHub API docs: https://docs.github.com/rest/releases/releases#list-releases 92 // 93 //meta:operation GET /repos/{owner}/{repo}/releases 94 func (s *RepositoriesService) ListReleases(ctx context.Context, owner, repo string, opts *ListOptions) ([]*RepositoryRelease, *Response, error) { 95 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 96 u, err := addOptions(u, opts) 97 if err != nil { 98 return nil, nil, err 99 } 100 101 req, err := s.client.NewRequest("GET", u, nil) 102 if err != nil { 103 return nil, nil, err 104 } 105 106 var releases []*RepositoryRelease 107 resp, err := s.client.Do(ctx, req, &releases) 108 if err != nil { 109 return nil, resp, err 110 } 111 return releases, resp, nil 112 } 113 114 // GetRelease fetches a single release. 115 // 116 // GitHub API docs: https://docs.github.com/rest/releases/releases#get-a-release 117 // 118 //meta:operation GET /repos/{owner}/{repo}/releases/{release_id} 119 func (s *RepositoriesService) GetRelease(ctx context.Context, owner, repo string, id int64) (*RepositoryRelease, *Response, error) { 120 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 121 return s.getSingleRelease(ctx, u) 122 } 123 124 // GetLatestRelease fetches the latest published release for the repository. 125 // 126 // GitHub API docs: https://docs.github.com/rest/releases/releases#get-the-latest-release 127 // 128 //meta:operation GET /repos/{owner}/{repo}/releases/latest 129 func (s *RepositoriesService) GetLatestRelease(ctx context.Context, owner, repo string) (*RepositoryRelease, *Response, error) { 130 u := fmt.Sprintf("repos/%s/%s/releases/latest", owner, repo) 131 return s.getSingleRelease(ctx, u) 132 } 133 134 // GetReleaseByTag fetches a release with the specified tag. 135 // 136 // GitHub API docs: https://docs.github.com/rest/releases/releases#get-a-release-by-tag-name 137 // 138 //meta:operation GET /repos/{owner}/{repo}/releases/tags/{tag} 139 func (s *RepositoriesService) GetReleaseByTag(ctx context.Context, owner, repo, tag string) (*RepositoryRelease, *Response, error) { 140 u := fmt.Sprintf("repos/%s/%s/releases/tags/%s", owner, repo, tag) 141 return s.getSingleRelease(ctx, u) 142 } 143 144 // GenerateReleaseNotes generates the release notes for the given tag. 145 // 146 // GitHub API docs: https://docs.github.com/rest/releases/releases#generate-release-notes-content-for-a-release 147 // 148 //meta:operation POST /repos/{owner}/{repo}/releases/generate-notes 149 func (s *RepositoriesService) GenerateReleaseNotes(ctx context.Context, owner, repo string, opts *GenerateNotesOptions) (*RepositoryReleaseNotes, *Response, error) { 150 u := fmt.Sprintf("repos/%s/%s/releases/generate-notes", owner, repo) 151 req, err := s.client.NewRequest("POST", u, opts) 152 if err != nil { 153 return nil, nil, err 154 } 155 156 r := new(RepositoryReleaseNotes) 157 resp, err := s.client.Do(ctx, req, r) 158 if err != nil { 159 return nil, resp, err 160 } 161 162 return r, resp, nil 163 } 164 165 func (s *RepositoriesService) getSingleRelease(ctx context.Context, url string) (*RepositoryRelease, *Response, error) { 166 req, err := s.client.NewRequest("GET", url, nil) 167 if err != nil { 168 return nil, nil, err 169 } 170 171 release := new(RepositoryRelease) 172 resp, err := s.client.Do(ctx, req, release) 173 if err != nil { 174 return nil, resp, err 175 } 176 return release, resp, nil 177 } 178 179 // repositoryReleaseRequest is a subset of RepositoryRelease and 180 // is used internally by CreateRelease and EditRelease to pass 181 // only the known fields for these endpoints. 182 // 183 // See https://github.com/google/go-github/issues/992 for more 184 // information. 185 type repositoryReleaseRequest struct { 186 TagName *string `json:"tag_name,omitempty"` 187 TargetCommitish *string `json:"target_commitish,omitempty"` 188 Name *string `json:"name,omitempty"` 189 Body *string `json:"body,omitempty"` 190 Draft *bool `json:"draft,omitempty"` 191 Prerelease *bool `json:"prerelease,omitempty"` 192 MakeLatest *string `json:"make_latest,omitempty"` 193 GenerateReleaseNotes *bool `json:"generate_release_notes,omitempty"` 194 DiscussionCategoryName *string `json:"discussion_category_name,omitempty"` 195 } 196 197 // CreateRelease adds a new release for a repository. 198 // 199 // Note that only a subset of the release fields are used. 200 // See RepositoryRelease for more information. 201 // 202 // GitHub API docs: https://docs.github.com/rest/releases/releases#create-a-release 203 // 204 //meta:operation POST /repos/{owner}/{repo}/releases 205 func (s *RepositoriesService) CreateRelease(ctx context.Context, owner, repo string, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 206 u := fmt.Sprintf("repos/%s/%s/releases", owner, repo) 207 208 releaseReq := &repositoryReleaseRequest{ 209 TagName: release.TagName, 210 TargetCommitish: release.TargetCommitish, 211 Name: release.Name, 212 Body: release.Body, 213 Draft: release.Draft, 214 Prerelease: release.Prerelease, 215 MakeLatest: release.MakeLatest, 216 DiscussionCategoryName: release.DiscussionCategoryName, 217 GenerateReleaseNotes: release.GenerateReleaseNotes, 218 } 219 220 req, err := s.client.NewRequest("POST", u, releaseReq) 221 if err != nil { 222 return nil, nil, err 223 } 224 225 r := new(RepositoryRelease) 226 resp, err := s.client.Do(ctx, req, r) 227 if err != nil { 228 return nil, resp, err 229 } 230 return r, resp, nil 231 } 232 233 // EditRelease edits a repository release. 234 // 235 // Note that only a subset of the release fields are used. 236 // See RepositoryRelease for more information. 237 // 238 // GitHub API docs: https://docs.github.com/rest/releases/releases#update-a-release 239 // 240 //meta:operation PATCH /repos/{owner}/{repo}/releases/{release_id} 241 func (s *RepositoriesService) EditRelease(ctx context.Context, owner, repo string, id int64, release *RepositoryRelease) (*RepositoryRelease, *Response, error) { 242 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 243 244 releaseReq := &repositoryReleaseRequest{ 245 TagName: release.TagName, 246 TargetCommitish: release.TargetCommitish, 247 Name: release.Name, 248 Body: release.Body, 249 Draft: release.Draft, 250 Prerelease: release.Prerelease, 251 MakeLatest: release.MakeLatest, 252 DiscussionCategoryName: release.DiscussionCategoryName, 253 } 254 255 req, err := s.client.NewRequest("PATCH", u, releaseReq) 256 if err != nil { 257 return nil, nil, err 258 } 259 260 r := new(RepositoryRelease) 261 resp, err := s.client.Do(ctx, req, r) 262 if err != nil { 263 return nil, resp, err 264 } 265 return r, resp, nil 266 } 267 268 // DeleteRelease delete a single release from a repository. 269 // 270 // GitHub API docs: https://docs.github.com/rest/releases/releases#delete-a-release 271 // 272 //meta:operation DELETE /repos/{owner}/{repo}/releases/{release_id} 273 func (s *RepositoriesService) DeleteRelease(ctx context.Context, owner, repo string, id int64) (*Response, error) { 274 u := fmt.Sprintf("repos/%s/%s/releases/%d", owner, repo, id) 275 276 req, err := s.client.NewRequest("DELETE", u, nil) 277 if err != nil { 278 return nil, err 279 } 280 return s.client.Do(ctx, req, nil) 281 } 282 283 // ListReleaseAssets lists the release's assets. 284 // 285 // GitHub API docs: https://docs.github.com/rest/releases/assets#list-release-assets 286 // 287 //meta:operation GET /repos/{owner}/{repo}/releases/{release_id}/assets 288 func (s *RepositoriesService) ListReleaseAssets(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*ReleaseAsset, *Response, error) { 289 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 290 u, err := addOptions(u, opts) 291 if err != nil { 292 return nil, nil, err 293 } 294 295 req, err := s.client.NewRequest("GET", u, nil) 296 if err != nil { 297 return nil, nil, err 298 } 299 300 var assets []*ReleaseAsset 301 resp, err := s.client.Do(ctx, req, &assets) 302 if err != nil { 303 return nil, resp, err 304 } 305 return assets, resp, nil 306 } 307 308 // GetReleaseAsset fetches a single release asset. 309 // 310 // GitHub API docs: https://docs.github.com/rest/releases/assets#get-a-release-asset 311 // 312 //meta:operation GET /repos/{owner}/{repo}/releases/assets/{asset_id} 313 func (s *RepositoriesService) GetReleaseAsset(ctx context.Context, owner, repo string, id int64) (*ReleaseAsset, *Response, error) { 314 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 315 316 req, err := s.client.NewRequest("GET", u, nil) 317 if err != nil { 318 return nil, nil, err 319 } 320 321 asset := new(ReleaseAsset) 322 resp, err := s.client.Do(ctx, req, asset) 323 if err != nil { 324 return nil, resp, err 325 } 326 return asset, resp, nil 327 } 328 329 // DownloadReleaseAsset downloads a release asset or returns a redirect URL. 330 // 331 // DownloadReleaseAsset returns an io.ReadCloser that reads the contents of the 332 // specified release asset. It is the caller's responsibility to close the ReadCloser. 333 // If a redirect is returned, the redirect URL will be returned as a string instead 334 // of the io.ReadCloser. Exactly one of rc and redirectURL will be zero. 335 // 336 // followRedirectsClient can be passed to download the asset from a redirected 337 // location. Specifying any http.Client is possible, but passing http.DefaultClient 338 // is recommended, except when the specified repository is private, in which case 339 // it's necessary to pass an http.Client that performs authenticated requests. 340 // If nil is passed the redirectURL will be returned instead. 341 // 342 // GitHub API docs: https://docs.github.com/rest/releases/assets#get-a-release-asset 343 // 344 //meta:operation GET /repos/{owner}/{repo}/releases/assets/{asset_id} 345 func (s *RepositoriesService) DownloadReleaseAsset(ctx context.Context, owner, repo string, id int64, followRedirectsClient *http.Client) (rc io.ReadCloser, redirectURL string, err error) { 346 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 347 348 req, err := s.client.NewRequest("GET", u, nil) 349 if err != nil { 350 return nil, "", err 351 } 352 req.Header.Set("Accept", defaultMediaType) 353 354 s.client.clientMu.Lock() 355 defer s.client.clientMu.Unlock() 356 357 var loc string 358 saveRedirect := s.client.client.CheckRedirect 359 s.client.client.CheckRedirect = func(req *http.Request, _ []*http.Request) error { 360 loc = req.URL.String() 361 return errors.New("disable redirect") 362 } 363 defer func() { s.client.client.CheckRedirect = saveRedirect }() 364 365 req = withContext(ctx, req) 366 resp, err := s.client.client.Do(req) 367 if err != nil { 368 if !strings.Contains(err.Error(), "disable redirect") { 369 return nil, "", err 370 } 371 if followRedirectsClient != nil { 372 rc, err := s.downloadReleaseAssetFromURL(ctx, followRedirectsClient, loc) 373 return rc, "", err 374 } 375 return nil, loc, nil // Intentionally return no error with valid redirect URL. 376 } 377 378 if err := CheckResponse(resp); err != nil { 379 _ = resp.Body.Close() 380 return nil, "", err 381 } 382 383 return resp.Body, "", nil 384 } 385 386 func (s *RepositoriesService) downloadReleaseAssetFromURL(ctx context.Context, followRedirectsClient *http.Client, url string) (rc io.ReadCloser, err error) { 387 req, err := http.NewRequest("GET", url, nil) 388 if err != nil { 389 return nil, err 390 } 391 req = withContext(ctx, req) 392 req.Header.Set("Accept", defaultMediaType) 393 resp, err := followRedirectsClient.Do(req) 394 if err != nil { 395 return nil, err 396 } 397 if err := CheckResponse(resp); err != nil { 398 _ = resp.Body.Close() 399 return nil, err 400 } 401 return resp.Body, nil 402 } 403 404 // EditReleaseAsset edits a repository release asset. 405 // 406 // GitHub API docs: https://docs.github.com/rest/releases/assets#update-a-release-asset 407 // 408 //meta:operation PATCH /repos/{owner}/{repo}/releases/assets/{asset_id} 409 func (s *RepositoriesService) EditReleaseAsset(ctx context.Context, owner, repo string, id int64, release *ReleaseAsset) (*ReleaseAsset, *Response, error) { 410 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 411 412 req, err := s.client.NewRequest("PATCH", u, release) 413 if err != nil { 414 return nil, nil, err 415 } 416 417 asset := new(ReleaseAsset) 418 resp, err := s.client.Do(ctx, req, asset) 419 if err != nil { 420 return nil, resp, err 421 } 422 return asset, resp, nil 423 } 424 425 // DeleteReleaseAsset delete a single release asset from a repository. 426 // 427 // GitHub API docs: https://docs.github.com/rest/releases/assets#delete-a-release-asset 428 // 429 //meta:operation DELETE /repos/{owner}/{repo}/releases/assets/{asset_id} 430 func (s *RepositoriesService) DeleteReleaseAsset(ctx context.Context, owner, repo string, id int64) (*Response, error) { 431 u := fmt.Sprintf("repos/%s/%s/releases/assets/%d", owner, repo, id) 432 433 req, err := s.client.NewRequest("DELETE", u, nil) 434 if err != nil { 435 return nil, err 436 } 437 return s.client.Do(ctx, req, nil) 438 } 439 440 // UploadReleaseAsset creates an asset by uploading a file into a release repository. 441 // To upload assets that cannot be represented by an os.File, call NewUploadRequest directly. 442 // 443 // GitHub API docs: https://docs.github.com/rest/releases/assets#upload-a-release-asset 444 // 445 //meta:operation POST /repos/{owner}/{repo}/releases/{release_id}/assets 446 func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, repo string, id int64, opts *UploadOptions, file *os.File) (*ReleaseAsset, *Response, error) { 447 u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", owner, repo, id) 448 u, err := addOptions(u, opts) 449 if err != nil { 450 return nil, nil, err 451 } 452 453 stat, err := file.Stat() 454 if err != nil { 455 return nil, nil, err 456 } 457 if stat.IsDir() { 458 return nil, nil, errors.New("the asset to upload can't be a directory") 459 } 460 461 mediaType := mime.TypeByExtension(filepath.Ext(file.Name())) 462 if opts.MediaType != "" { 463 mediaType = opts.MediaType 464 } 465 466 req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType) 467 if err != nil { 468 return nil, nil, err 469 } 470 471 asset := new(ReleaseAsset) 472 resp, err := s.client.Do(ctx, req, asset) 473 if err != nil { 474 return nil, resp, err 475 } 476 return asset, resp, nil 477 }