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