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-05 15:37:44 +02:00
ctx , client , err := piperGithub . NewClient ( myGithubPublishReleaseOptions . GithubToken , myGithubPublishReleaseOptions . GithubAPIURL , myGithubPublishReleaseOptions . GithubUploadURL )
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-04 17:07:30 +02:00
lastRelease , resp , err := ghRepoClient . GetLatestRelease ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo )
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 )
if myGithubPublishReleaseOptions . UpdateAsset {
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 ,
}
createdRelease , _ , err := ghRepoClient . CreateRelease ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo , & release )
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 15:37:44 +02:00
log . Entry ( ) . Infof ( "Release %v created on %v/%v" , * createdRelease . TagName , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo )
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
}
ghIssues , _ , err := ghIssueClient . ListByRepo ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo , & options )
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 15:37:44 +02:00
prTexts := [ ] string { "\n**List of closed pull-requests since last release**" }
issueTexts := [ ] string { "\n**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 {
closedIssuesText += strings . Join ( prTexts , "\n" ) + "\n"
}
if len ( issueTexts ) > 1 {
closedIssuesText += strings . Join ( issueTexts , "\n" ) + "\n"
}
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 ,
myGithubPublishReleaseOptions . GithubServerURL ,
myGithubPublishReleaseOptions . GithubOrg ,
myGithubPublishReleaseOptions . GithubRepo ,
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 {
assets , _ , err := ghRepoClient . ListReleaseAssets ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo , releaseID , & github . ListOptions { } )
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
_ , err := ghRepoClient . DeleteReleaseAsset ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo , assetID )
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." )
asset , _ , err := ghRepoClient . UploadReleaseAsset ( ctx , myGithubPublishReleaseOptions . GithubOrg , myGithubPublishReleaseOptions . GithubRepo , releaseID , & opts , file )
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
}