2019-11-04 17:07:30 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2019-11-05 15:37:44 +02:00
|
|
|
"mime"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-11-04 17:07:30 +02:00
|
|
|
"strings"
|
|
|
|
|
2019-11-05 15:37:44 +02:00
|
|
|
"github.com/SAP/jenkins-library/pkg/log"
|
2019-11-04 17:07:30 +02:00
|
|
|
"github.com/google/go-github/v28/github"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
|
|
|
piperGithub "github.com/SAP/jenkins-library/pkg/github"
|
|
|
|
)
|
|
|
|
|
|
|
|
type githubRepoClient interface {
|
|
|
|
CreateRelease(ctx context.Context, owner string, repo string, release *github.RepositoryRelease) (*github.RepositoryRelease, *github.Response, error)
|
2019-11-05 15:37:44 +02:00
|
|
|
DeleteReleaseAsset(ctx context.Context, owner string, repo string, id int64) (*github.Response, error)
|
|
|
|
GetLatestRelease(ctx context.Context, owner string, repo string) (*github.RepositoryRelease, *github.Response, error)
|
|
|
|
ListReleaseAssets(ctx context.Context, owner string, repo string, id int64, opt *github.ListOptions) ([]*github.ReleaseAsset, *github.Response, error)
|
|
|
|
UploadReleaseAsset(ctx context.Context, owner string, repo string, id int64, opt *github.UploadOptions, file *os.File) (*github.ReleaseAsset, *github.Response, error)
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type githubIssueClient interface {
|
|
|
|
ListByRepo(ctx context.Context, owner string, repo string, opt *github.IssueListByRepoOptions) ([]*github.Issue, *github.Response, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func githubPublishRelease(myGithubPublishReleaseOptions githubPublishReleaseOptions) error {
|
2019-11-06 10:12:50 +02:00
|
|
|
ctx, client, err := piperGithub.NewClient(myGithubPublishReleaseOptions.Token, myGithubPublishReleaseOptions.APIURL, myGithubPublishReleaseOptions.UploadURL)
|
2019-11-04 17:07:30 +02:00
|
|
|
if err != nil {
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().WithError(err).Fatal("Failed to get GitHub client.")
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err = runGithubPublishRelease(ctx, &myGithubPublishReleaseOptions, client.Repositories, client.Issues)
|
|
|
|
if err != nil {
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().WithError(err).Fatal("Failed to publish GitHub release.")
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runGithubPublishRelease(ctx context.Context, myGithubPublishReleaseOptions *githubPublishReleaseOptions, ghRepoClient githubRepoClient, ghIssueClient githubIssueClient) error {
|
|
|
|
|
|
|
|
var publishedAt github.Timestamp
|
2019-11-05 15:37:44 +02:00
|
|
|
|
2019-11-05 16:13:04 +02:00
|
|
|
lastRelease, resp, err := ghRepoClient.GetLatestRelease(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository)
|
2019-11-04 17:07:30 +02:00
|
|
|
if err != nil {
|
|
|
|
if resp.StatusCode == 404 {
|
2019-11-05 15:37:44 +02:00
|
|
|
//no previous release found -> first release
|
2019-11-04 17:07:30 +02:00
|
|
|
myGithubPublishReleaseOptions.AddDeltaToLastRelease = false
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().Debug("This is the first release.")
|
2019-11-04 17:07:30 +02:00
|
|
|
} else {
|
2019-11-05 15:37:44 +02:00
|
|
|
return errors.Wrap(err, "Error occured when retrieving latest GitHub release.")
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
}
|
2019-11-05 15:37:44 +02:00
|
|
|
publishedAt = lastRelease.GetPublishedAt()
|
|
|
|
log.Entry().Debugf("Previous GitHub release published: '%v'", publishedAt)
|
|
|
|
|
2019-11-06 10:05:07 +02:00
|
|
|
//updating assets only supported on latest release
|
2019-11-21 17:34:22 +02:00
|
|
|
if len(myGithubPublishReleaseOptions.AssetPath) > 0 && myGithubPublishReleaseOptions.Version == "latest" {
|
2019-11-05 15:37:44 +02:00
|
|
|
return uploadReleaseAsset(ctx, lastRelease.GetID(), myGithubPublishReleaseOptions, ghRepoClient)
|
|
|
|
}
|
2019-11-04 17:07:30 +02:00
|
|
|
|
|
|
|
releaseBody := ""
|
|
|
|
|
|
|
|
if len(myGithubPublishReleaseOptions.ReleaseBodyHeader) > 0 {
|
2019-11-05 15:37:44 +02:00
|
|
|
releaseBody += myGithubPublishReleaseOptions.ReleaseBodyHeader + "\n"
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if myGithubPublishReleaseOptions.AddClosedIssues {
|
|
|
|
releaseBody += getClosedIssuesText(ctx, publishedAt, myGithubPublishReleaseOptions, ghIssueClient)
|
|
|
|
}
|
|
|
|
|
|
|
|
if myGithubPublishReleaseOptions.AddDeltaToLastRelease {
|
|
|
|
releaseBody += getReleaseDeltaText(myGithubPublishReleaseOptions, lastRelease)
|
|
|
|
}
|
|
|
|
|
|
|
|
release := github.RepositoryRelease{
|
|
|
|
TagName: &myGithubPublishReleaseOptions.Version,
|
|
|
|
TargetCommitish: &myGithubPublishReleaseOptions.Commitish,
|
|
|
|
Name: &myGithubPublishReleaseOptions.Version,
|
|
|
|
Body: &releaseBody,
|
|
|
|
}
|
|
|
|
|
2019-11-05 16:13:04 +02:00
|
|
|
createdRelease, _, err := ghRepoClient.CreateRelease(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository, &release)
|
2019-11-04 17:07:30 +02:00
|
|
|
if err != nil {
|
2019-11-05 15:37:44 +02:00
|
|
|
return errors.Wrapf(err, "Creation of release '%v' failed", *release.TagName)
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
2019-11-05 16:13:04 +02:00
|
|
|
log.Entry().Infof("Release %v created on %v/%v", *createdRelease.TagName, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository)
|
2019-11-04 17:07:30 +02:00
|
|
|
|
2019-11-05 15:37:44 +02:00
|
|
|
if len(myGithubPublishReleaseOptions.AssetPath) > 0 {
|
|
|
|
return uploadReleaseAsset(ctx, createdRelease.GetID(), myGithubPublishReleaseOptions, ghRepoClient)
|
|
|
|
}
|
2019-11-04 17:07:30 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getClosedIssuesText(ctx context.Context, publishedAt github.Timestamp, myGithubPublishReleaseOptions *githubPublishReleaseOptions, ghIssueClient githubIssueClient) string {
|
|
|
|
closedIssuesText := ""
|
2019-11-05 15:37:44 +02:00
|
|
|
|
2019-11-04 17:07:30 +02:00
|
|
|
options := github.IssueListByRepoOptions{
|
|
|
|
State: "closed",
|
|
|
|
Direction: "asc",
|
|
|
|
Since: publishedAt.Time,
|
|
|
|
}
|
|
|
|
if len(myGithubPublishReleaseOptions.Labels) > 0 {
|
|
|
|
options.Labels = myGithubPublishReleaseOptions.Labels
|
|
|
|
}
|
2019-11-05 16:13:04 +02:00
|
|
|
ghIssues, _, err := ghIssueClient.ListByRepo(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository, &options)
|
2019-11-04 17:07:30 +02:00
|
|
|
if err != nil {
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().WithError(err).Error("Failed to get GitHub issues.")
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
2019-11-05 18:33:00 +02:00
|
|
|
prTexts := []string{"**List of closed pull-requests since last release**"}
|
|
|
|
issueTexts := []string{"**List of closed issues since last release**"}
|
2019-11-04 17:07:30 +02:00
|
|
|
|
|
|
|
for _, issue := range ghIssues {
|
|
|
|
if issue.IsPullRequest() && !isExcluded(issue, myGithubPublishReleaseOptions.ExcludeLabels) {
|
|
|
|
prTexts = append(prTexts, fmt.Sprintf("[#%v](%v): %v", issue.GetNumber(), issue.GetHTMLURL(), issue.GetTitle()))
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().Debugf("Added PR #%v to release", issue.GetNumber())
|
2019-11-04 17:07:30 +02:00
|
|
|
} else if !issue.IsPullRequest() && !isExcluded(issue, myGithubPublishReleaseOptions.ExcludeLabels) {
|
|
|
|
issueTexts = append(issueTexts, fmt.Sprintf("[#%v](%v): %v", issue.GetNumber(), issue.GetHTMLURL(), issue.GetTitle()))
|
2019-11-05 15:37:44 +02:00
|
|
|
log.Entry().Debugf("Added Issue #%v to release", issue.GetNumber())
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(prTexts) > 1 {
|
2019-11-05 18:33:00 +02:00
|
|
|
closedIssuesText += "\n" + strings.Join(prTexts, "\n") + "\n"
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(issueTexts) > 1 {
|
2019-11-05 18:33:00 +02:00
|
|
|
closedIssuesText += "\n" + strings.Join(issueTexts, "\n") + "\n"
|
2019-11-04 17:07:30 +02:00
|
|
|
}
|
|
|
|
return closedIssuesText
|
|
|
|
}
|
|
|
|
|
|
|
|
func getReleaseDeltaText(myGithubPublishReleaseOptions *githubPublishReleaseOptions, lastRelease *github.RepositoryRelease) string {
|
|
|
|
releaseDeltaText := ""
|
|
|
|
|
|
|
|
//add delta link to previous release
|
2019-11-05 15:37:44 +02:00
|
|
|
releaseDeltaText += "\n**Changes**\n"
|
2019-11-04 17:07:30 +02:00
|
|
|
releaseDeltaText += fmt.Sprintf(
|
2019-11-05 15:37:44 +02:00
|
|
|
"[%v...%v](%v/%v/%v/compare/%v...%v)\n",
|
2019-11-04 17:07:30 +02:00
|
|
|
lastRelease.GetTagName(),
|
|
|
|
myGithubPublishReleaseOptions.Version,
|
2019-11-05 16:13:04 +02:00
|
|
|
myGithubPublishReleaseOptions.ServerURL,
|
|
|
|
myGithubPublishReleaseOptions.Owner,
|
|
|
|
myGithubPublishReleaseOptions.Repository,
|
2019-11-04 17:07:30 +02:00
|
|
|
lastRelease.GetTagName(), myGithubPublishReleaseOptions.Version,
|
|
|
|
)
|
|
|
|
|
|
|
|
return releaseDeltaText
|
|
|
|
}
|
|
|
|
|
2019-11-05 15:37:44 +02:00
|
|
|
func uploadReleaseAsset(ctx context.Context, releaseID int64, myGithubPublishReleaseOptions *githubPublishReleaseOptions, ghRepoClient githubRepoClient) error {
|
|
|
|
|
2019-11-05 16:13:04 +02:00
|
|
|
assets, _, err := ghRepoClient.ListReleaseAssets(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository, releaseID, &github.ListOptions{})
|
2019-11-05 15:37:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed to get list of release assets.")
|
|
|
|
}
|
|
|
|
var assetID int64
|
|
|
|
for _, a := range assets {
|
|
|
|
if a.GetName() == filepath.Base(myGithubPublishReleaseOptions.AssetPath) {
|
|
|
|
assetID = a.GetID()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if assetID != 0 {
|
|
|
|
//asset needs to be deleted first since API does not allow for replacement
|
2019-11-05 16:13:04 +02:00
|
|
|
_, err := ghRepoClient.DeleteReleaseAsset(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository, assetID)
|
2019-11-05 15:37:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed to delete release asset.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mediaType := mime.TypeByExtension(filepath.Ext(myGithubPublishReleaseOptions.AssetPath))
|
|
|
|
if mediaType == "" {
|
|
|
|
mediaType = "application/octet-stream"
|
|
|
|
}
|
|
|
|
log.Entry().Debugf("Using mediaType '%v'", mediaType)
|
|
|
|
|
|
|
|
name := filepath.Base(myGithubPublishReleaseOptions.AssetPath)
|
|
|
|
log.Entry().Debugf("Using file name '%v'", name)
|
|
|
|
|
|
|
|
opts := github.UploadOptions{
|
|
|
|
Name: name,
|
|
|
|
MediaType: mediaType,
|
|
|
|
}
|
|
|
|
file, err := os.Open(myGithubPublishReleaseOptions.AssetPath)
|
|
|
|
defer file.Close()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "Failed to load release asset '%v'", myGithubPublishReleaseOptions.AssetPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Entry().Info("Starting to upload release asset.")
|
2019-11-05 16:13:04 +02:00
|
|
|
asset, _, err := ghRepoClient.UploadReleaseAsset(ctx, myGithubPublishReleaseOptions.Owner, myGithubPublishReleaseOptions.Repository, releaseID, &opts, file)
|
2019-11-05 15:37:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed to upload release asset.")
|
|
|
|
}
|
|
|
|
log.Entry().Infof("Done uploading asset '%v'.", asset.GetURL())
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-04 17:07:30 +02:00
|
|
|
func isExcluded(issue *github.Issue, excludeLabels []string) bool {
|
|
|
|
//issue.Labels[0].GetName()
|
|
|
|
for _, ex := range excludeLabels {
|
|
|
|
for _, l := range issue.Labels {
|
|
|
|
if ex == l.GetName() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|