github.com/xgoffin/jenkins-library@v1.154.0/cmd/githubPublishRelease.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "mime" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/SAP/jenkins-library/pkg/log" 12 "github.com/SAP/jenkins-library/pkg/telemetry" 13 "github.com/google/go-github/v32/github" 14 "github.com/pkg/errors" 15 16 piperGithub "github.com/SAP/jenkins-library/pkg/github" 17 ) 18 19 type githubRepoClient interface { 20 CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error) 21 DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error) 22 GetLatestRelease(ctx context.Context, owner string, repo string) (*github.RepositoryRelease, *github.Response, error) 23 ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opt *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error) 24 UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error) 25 } 26 27 type githubIssueClient interface { 28 ListByRepo(ctx context.Context, owner string, repo string, opt *github.IssueListByRepoOptions) ([]*github.Issue, *github.Response, error) 29 } 30 31 func githubPublishRelease(config githubPublishReleaseOptions, telemetryData *telemetry.CustomData) { 32 //TODO provide parameter for trusted certs 33 ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, config.UploadURL, []string{}) 34 if err != nil { 35 log.Entry().WithError(err).Fatal("Failed to get GitHub client.") 36 } 37 38 err = runGithubPublishRelease(ctx, &config, client.Repositories, client.Issues) 39 if err != nil { 40 log.Entry().WithError(err).Fatal("Failed to publish GitHub release.") 41 } 42 } 43 44 func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOptions, ghRepoClient githubRepoClient, ghIssueClient githubIssueClient) error { 45 46 var publishedAt github.Timestamp 47 48 lastRelease, resp, err := ghRepoClient.GetLatestRelease(ctx, config.Owner, config.Repository) 49 if err != nil { 50 if resp != nil && resp.StatusCode == 404 { 51 //no previous release found -> first release 52 config.AddDeltaToLastRelease = false 53 log.Entry().Debug("This is the first release.") 54 } else { 55 return errors.Wrapf(err, "Error occurred when retrieving latest GitHub release (%v/%v)", config.Owner, config.Repository) 56 } 57 } 58 publishedAt = lastRelease.GetPublishedAt() 59 log.Entry().Debugf("Previous GitHub release published: '%v'", publishedAt) 60 61 //updating assets only supported on latest release 62 if len(config.AssetPath) > 0 && config.Version == "latest" { 63 return uploadReleaseAsset(ctx, lastRelease.GetID(), config, ghRepoClient) 64 } 65 66 releaseBody := "" 67 68 if len(config.ReleaseBodyHeader) > 0 { 69 releaseBody += config.ReleaseBodyHeader + "\n" 70 } 71 72 if config.AddClosedIssues { 73 releaseBody += getClosedIssuesText(ctx, publishedAt, config, ghIssueClient) 74 } 75 76 if config.AddDeltaToLastRelease { 77 releaseBody += getReleaseDeltaText(config, lastRelease) 78 } 79 80 prefixedTagName := config.TagPrefix + config.Version 81 82 release := github.RepositoryRelease{ 83 TagName: &prefixedTagName, 84 TargetCommitish: &config.Commitish, 85 Name: &config.Version, 86 Body: &releaseBody, 87 Prerelease: &config.PreRelease, 88 } 89 90 createdRelease, _, err := ghRepoClient.CreateRelease(ctx, config.Owner, config.Repository, &release) 91 if err != nil { 92 return errors.Wrapf(err, "Creation of release '%v' failed", *release.TagName) 93 } 94 log.Entry().Infof("Release %v created on %v/%v", *createdRelease.TagName, config.Owner, config.Repository) 95 96 if len(config.AssetPath) > 0 { 97 return uploadReleaseAsset(ctx, createdRelease.GetID(), config, ghRepoClient) 98 } 99 100 return nil 101 } 102 103 func getClosedIssuesText(ctx context.Context, publishedAt github.Timestamp, config *githubPublishReleaseOptions, ghIssueClient githubIssueClient) string { 104 closedIssuesText := "" 105 106 options := github.IssueListByRepoOptions{ 107 State: "closed", 108 Direction: "asc", 109 Since: publishedAt.Time, 110 } 111 if len(config.Labels) > 0 { 112 options.Labels = config.Labels 113 } 114 ghIssues, _, err := ghIssueClient.ListByRepo(ctx, config.Owner, config.Repository, &options) 115 if err != nil { 116 log.Entry().WithError(err).Error("Failed to get GitHub issues.") 117 } 118 119 prTexts := []string{"**List of closed pull-requests since last release**"} 120 issueTexts := []string{"**List of closed issues since last release**"} 121 122 for _, issue := range ghIssues { 123 if issue.IsPullRequest() && !isExcluded(issue, config.ExcludeLabels) { 124 prTexts = append(prTexts, fmt.Sprintf("[#%v](%v): %v", issue.GetNumber(), issue.GetHTMLURL(), issue.GetTitle())) 125 log.Entry().Debugf("Added PR #%v to release", issue.GetNumber()) 126 } else if !issue.IsPullRequest() && !isExcluded(issue, config.ExcludeLabels) { 127 issueTexts = append(issueTexts, fmt.Sprintf("[#%v](%v): %v", issue.GetNumber(), issue.GetHTMLURL(), issue.GetTitle())) 128 log.Entry().Debugf("Added Issue #%v to release", issue.GetNumber()) 129 } 130 } 131 132 if len(prTexts) > 1 { 133 closedIssuesText += "\n" + strings.Join(prTexts, "\n") + "\n" 134 } 135 136 if len(issueTexts) > 1 { 137 closedIssuesText += "\n" + strings.Join(issueTexts, "\n") + "\n" 138 } 139 return closedIssuesText 140 } 141 142 func getReleaseDeltaText(config *githubPublishReleaseOptions, lastRelease *github.RepositoryRelease) string { 143 releaseDeltaText := "" 144 145 //add delta link to previous release 146 releaseDeltaText += "\n**Changes**\n" 147 releaseDeltaText += fmt.Sprintf( 148 "[%v...%v](%v/%v/%v/compare/%v...%v)\n", 149 lastRelease.GetTagName(), 150 config.Version, 151 config.ServerURL, 152 config.Owner, 153 config.Repository, 154 lastRelease.GetTagName(), config.Version, 155 ) 156 157 return releaseDeltaText 158 } 159 160 func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPublishReleaseOptions, ghRepoClient githubRepoClient) error { 161 162 assets, _, err := ghRepoClient.ListReleaseAssets(ctx, config.Owner, config.Repository, releaseID, &github.ListOptions{}) 163 if err != nil { 164 return errors.Wrap(err, "Failed to get list of release assets.") 165 } 166 var assetID int64 167 for _, a := range assets { 168 if a.GetName() == filepath.Base(config.AssetPath) { 169 assetID = a.GetID() 170 break 171 } 172 } 173 if assetID != 0 { 174 //asset needs to be deleted first since API does not allow for replacement 175 _, err := ghRepoClient.DeleteReleaseAsset(ctx, config.Owner, config.Repository, assetID) 176 if err != nil { 177 return errors.Wrap(err, "Failed to delete release asset.") 178 } 179 } 180 181 mediaType := mime.TypeByExtension(filepath.Ext(config.AssetPath)) 182 if mediaType == "" { 183 mediaType = "application/octet-stream" 184 } 185 log.Entry().Debugf("Using mediaType '%v'", mediaType) 186 187 name := filepath.Base(config.AssetPath) 188 log.Entry().Debugf("Using file name '%v'", name) 189 190 opts := github.UploadOptions{ 191 Name: name, 192 MediaType: mediaType, 193 } 194 file, err := os.Open(config.AssetPath) 195 defer file.Close() 196 if err != nil { 197 return errors.Wrapf(err, "Failed to load release asset '%v'", config.AssetPath) 198 } 199 200 log.Entry().Info("Starting to upload release asset.") 201 asset, _, err := ghRepoClient.UploadReleaseAsset(ctx, config.Owner, config.Repository, releaseID, &opts, file) 202 if err != nil { 203 return errors.Wrap(err, "Failed to upload release asset.") 204 } 205 log.Entry().Infof("Done uploading asset '%v'.", asset.GetURL()) 206 207 return nil 208 } 209 210 func isExcluded(issue *github.Issue, excludeLabels []string) bool { 211 //issue.Labels[0].GetName() 212 for _, ex := range excludeLabels { 213 for _, l := range issue.Labels { 214 if ex == l.GetName() { 215 return true 216 } 217 } 218 } 219 return false 220 }