1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/githubPublishRelease.go

238 lines
8.2 KiB
Go
Raw Normal View History

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"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/google/go-github/v45/github"
2019-11-04 17:07:30 +02:00
"github.com/pkg/errors"
piperGithub "github.com/SAP/jenkins-library/pkg/github"
)
// mock generated with: mockery --name GithubRepoClient --dir cmd --output cmd/mocks
type GithubRepoClient interface {
2019-11-04 17:07:30 +02:00
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(config githubPublishReleaseOptions, telemetryData *telemetry.CustomData) {
// TODO provide parameter for trusted certs
feat(whitesourceExecuteScan): GitHub issue creation + SARIF (#3535) * Add GH issue creation + SARIF * Code cleanup * Fix fmt, add debug * Code enhancements * Fix * Added debug info * Rework UA log scan * Fix code * read UA version * Fix nil reference * Extraction * Credentials * Issue creation * Error handling * Fix issue creation * query escape * Query escape 2 * Revert * Test avoid update * HTTP client * Add support for custom TLS certs * Fix code * Fix code 2 * Fix code 3 * Disable cert check * Fix auth * Remove implicit trust * Skip verification * Fix * Fix client * Fix HTTP auth * Fix trusted certs * Trim version * Code * Add token * Added token handling to client * Fix token * Cleanup * Fix token * Token rework * Fix code * Kick out oauth client * Kick out oauth client * Transport wrapping * Token * Simplification * Refactor * Variation * Check * Fix * Debug * Switch client * Variation * Debug * Switch to cert check * Add debug * Parse self * Cleanup * Update resources/metadata/whitesourceExecuteScan.yaml * Add debug * Expose subjects * Patch * Debug * Debug2 * Debug3 * Fix logging response body * Cleanup * Cleanup * Fix request body logging * Cleanup import * Fix import cycle * Cleanup * Fix fmt * Fix NopCloser reference * Regenerate * Reintroduce * Fix test * Fix tests * Correction * Fix error * Code fix * Fix tests * Add tests * Fix code climate issues * Code climate * Code climate again * Code climate again * Fix fmt * Fix fmt 2 Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
2022-02-23 10:30:19 +02:00
ctx, client, err := piperGithub.NewClient(config.Token, config.APIURL, config.UploadURL, []string{})
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, &config, client.Repositories, client.Issues)
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 publish GitHub release.")
2019-11-04 17:07:30 +02:00
}
}
func runGithubPublishRelease(ctx context.Context, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient, ghIssueClient githubIssueClient) error {
2019-11-04 17:07:30 +02:00
var publishedAt github.Timestamp
2019-11-05 15:37:44 +02:00
lastRelease, resp, err := ghRepoClient.GetLatestRelease(ctx, config.Owner, config.Repository)
2019-11-04 17:07:30 +02:00
if err != nil {
if resp != nil && resp.StatusCode == 404 {
// no previous release found -> first release
config.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 {
return errors.Wrapf(err, "Error occurred when retrieving latest GitHub release (%v/%v)", config.Owner, config.Repository)
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)
// updating assets only supported on latest release
if len(config.AssetPath) > 0 && config.Version == "latest" {
return uploadReleaseAsset(ctx, lastRelease.GetID(), config, ghRepoClient)
} else if len(config.AssetPathList) > 0 && config.Version == "latest" {
return uploadReleaseAssetList(ctx, lastRelease.GetID(), config, ghRepoClient)
2019-11-05 15:37:44 +02:00
}
2019-11-04 17:07:30 +02:00
releaseBody := ""
if len(config.ReleaseBodyHeader) > 0 {
releaseBody += config.ReleaseBodyHeader + "\n"
2019-11-04 17:07:30 +02:00
}
if config.AddClosedIssues {
releaseBody += getClosedIssuesText(ctx, publishedAt, config, ghIssueClient)
2019-11-04 17:07:30 +02:00
}
if config.AddDeltaToLastRelease {
releaseBody += getReleaseDeltaText(config, lastRelease)
2019-11-04 17:07:30 +02:00
}
prefixedTagName := config.TagPrefix + config.Version
2019-11-04 17:07:30 +02:00
release := github.RepositoryRelease{
TagName: &prefixedTagName,
TargetCommitish: &config.Commitish,
Name: &config.Version,
2019-11-04 17:07:30 +02:00
Body: &releaseBody,
Prerelease: &config.PreRelease,
2019-11-04 17:07:30 +02:00
}
createdRelease, _, err := ghRepoClient.CreateRelease(ctx, config.Owner, config.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
}
log.Entry().Infof("Release %v created on %v/%v", *createdRelease.TagName, config.Owner, config.Repository)
2019-11-04 17:07:30 +02:00
if len(config.AssetPath) > 0 {
return uploadReleaseAsset(ctx, createdRelease.GetID(), config, ghRepoClient)
} else if len(config.AssetPathList) > 0 {
return uploadReleaseAssetList(ctx, createdRelease.GetID(), config, ghRepoClient)
2019-11-05 15:37:44 +02:00
}
2019-11-04 17:07:30 +02:00
return nil
}
func getClosedIssuesText(ctx context.Context, publishedAt github.Timestamp, config *githubPublishReleaseOptions, ghIssueClient githubIssueClient) string {
2019-11-04 17:07:30 +02:00
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(config.Labels) > 0 {
options.Labels = config.Labels
2019-11-04 17:07:30 +02:00
}
ghIssues, _, err := ghIssueClient.ListByRepo(ctx, config.Owner, config.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, config.ExcludeLabels) {
2019-11-04 17:07:30 +02:00
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())
} else if !issue.IsPullRequest() && !isExcluded(issue, config.ExcludeLabels) {
2019-11-04 17:07:30 +02:00
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(config *githubPublishReleaseOptions, lastRelease *github.RepositoryRelease) string {
2019-11-04 17:07:30 +02:00
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(),
config.Version,
config.ServerURL,
config.Owner,
config.Repository,
lastRelease.GetTagName(), config.Version,
2019-11-04 17:07:30 +02:00
)
return releaseDeltaText
}
func uploadReleaseAssetList(ctx context.Context, releaseID int64, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient) error {
for _, asset := range config.AssetPathList {
config.AssetPath = asset
err := uploadReleaseAsset(ctx, releaseID, config, ghRepoClient)
if err != nil {
return fmt.Errorf("failed to upload release asset: %w", err)
}
}
return nil
}
func uploadReleaseAsset(ctx context.Context, releaseID int64, config *githubPublishReleaseOptions, ghRepoClient GithubRepoClient) error {
assets, _, err := ghRepoClient.ListReleaseAssets(ctx, config.Owner, config.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(config.AssetPath) {
2019-11-05 15:37:44 +02:00
assetID = a.GetID()
break
}
}
if assetID != 0 {
// asset needs to be deleted first since API does not allow for replacement
_, err := ghRepoClient.DeleteReleaseAsset(ctx, config.Owner, config.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(config.AssetPath))
2019-11-05 15:37:44 +02:00
if mediaType == "" {
mediaType = "application/octet-stream"
}
log.Entry().Debugf("Using mediaType '%v'", mediaType)
name := filepath.Base(config.AssetPath)
2019-11-05 15:37:44 +02:00
log.Entry().Debugf("Using file name '%v'", name)
opts := github.UploadOptions{
Name: name,
MediaType: mediaType,
}
file, err := os.Open(config.AssetPath)
if err != nil {
return fmt.Errorf("failed to open release asset %v: %w", config.AssetPath, err)
}
2019-11-05 15:37:44 +02:00
defer file.Close()
if err != nil {
return errors.Wrapf(err, "Failed to load release asset '%v'", config.AssetPath)
2019-11-05 15:37:44 +02:00
}
log.Entry().Info("Starting to upload release asset.")
asset, _, err := ghRepoClient.UploadReleaseAsset(ctx, config.Owner, config.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()
2019-11-04 17:07:30 +02:00
for _, ex := range excludeLabels {
for _, l := range issue.Labels {
if ex == l.GetName() {
return true
}
}
}
return false
}