From e170ca9885b07743ad26987693fdc5bccc312fb8 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Sun, 28 Jul 2019 23:00:08 +0200 Subject: [PATCH 1/9] test(releaser/github): test New func --- internal/releaser/github/github_test.go | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 internal/releaser/github/github_test.go diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go new file mode 100644 index 0000000..3929027 --- /dev/null +++ b/internal/releaser/github/github_test.go @@ -0,0 +1,44 @@ +package github_test + +import ( + "github.com/Nightapes/go-semantic-release/internal/releaser/github" + "github.com/Nightapes/go-semantic-release/pkg/config" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +type testDoubleNew struct { + config config.GitHubProvider + valid bool +} + +var doublesNew = []testDoubleNew{ + testDoubleNew{config: config.GitHubProvider{ + Repo: "foo", + User: "bar", + }, + valid: true, + }, + + testDoubleNew{config: config.GitHubProvider{ + Repo: "foo", + User: "bar", + }, + valid: false, + }, +} + +func TestNew(t *testing.T) { + for _, testOject := range doublesNew { + if testOject.valid { + os.Setenv("GITHUB_ACCESS_TOKEN", "XXX") + } + + _, err := github.New(&testOject.config) + assert.Equal(t, testOject.valid, err == nil) + + os.Unsetenv("GITHUB_ACCESS_TOKEN") + + } +} From 682fae323952ee8b8b4b7cd8801d079c13618cd4 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Mon, 29 Jul 2019 23:11:07 +0200 Subject: [PATCH 2/9] test(releaser/github): tests for GetCommitURL(), GetCompareURL(), ValisateConfig() --- internal/releaser/github/github_test.go | 86 +++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index 3929027..c8bb1d3 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -1,29 +1,55 @@ package github_test import ( + "fmt" + "os" + "testing" + "github.com/Nightapes/go-semantic-release/internal/releaser/github" "github.com/Nightapes/go-semantic-release/pkg/config" "github.com/stretchr/testify/assert" - "os" - "testing" ) -type testDoubleNew struct { +type testDouble struct { config config.GitHubProvider valid bool } -var doublesNew = []testDoubleNew{ - testDoubleNew{config: config.GitHubProvider{ +var doublesNew = []testDouble{ + testDouble{config: config.GitHubProvider{ Repo: "foo", User: "bar", }, valid: true, }, - testDoubleNew{config: config.GitHubProvider{ + testDouble{config: config.GitHubProvider{ + Repo: "foo", + User: "bar", + CustomURL: "https://test.com", + }, + valid: false, + }, +} + +var doublesValidateConfig = []testDouble{ + testDouble{config: config.GitHubProvider{ Repo: "foo", User: "bar", + }, + valid: true, + }, + + testDouble{config: config.GitHubProvider{ + Repo: "", + User: "bar", + }, + valid: false, + }, + + testDouble{config: config.GitHubProvider{ + Repo: "foo", + User: "", }, valid: false, }, @@ -42,3 +68,51 @@ func TestNew(t *testing.T) { } } + +func TestGetCommitURL(t *testing.T) { + os.Setenv("GITHUB_ACCESS_TOKEN", "XX") + for _, testOject := range doublesNew { + client, _ := github.New(&testOject.config) + actualUrl := client.GetCommitURL() + if testOject.config.CustomURL != "" { + expectedUrl := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo) + assert.EqualValues(t, expectedUrl, actualUrl) + + } else { + expectedUrl := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", "https://github.com", testOject.config.User, testOject.config.Repo) + assert.EqualValues(t, expectedUrl, actualUrl) + } + } + os.Unsetenv("GITHUB_ACCESS_TOKEN") + +} + +func TestGetCompareURL(t *testing.T) { + os.Setenv("GITHUB_ACCESS_TOKEN", "XX") + for _, testOject := range doublesNew { + client, _ := github.New(&testOject.config) + actualUrl := client.GetCompareURL("1", "2") + if testOject.config.CustomURL != "" { + expectedUrl := fmt.Sprintf("%s/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2") + assert.EqualValues(t, expectedUrl, actualUrl) + + } else { + expectedUrl := fmt.Sprintf("%s/%s/%s/compare/%s...%s", "https://github.com", testOject.config.User, testOject.config.Repo, "1", "2") + assert.EqualValues(t, expectedUrl, actualUrl) + } + } + os.Unsetenv("GITHUB_ACCESS_TOKEN") + +} + +func TestValidateConfig(t *testing.T) { + os.Setenv("GITHUB_ACCESS_TOKEN", "XX") + for _, testOject := range doublesValidateConfig { + client, _ := github.New(&testOject.config) + err := client.ValidateConfig() + + assert.Equal(t, testOject.valid, err == nil) + + } + os.Unsetenv("GITHUB_ACCESS_TOKEN") +} From bdc4fb1d747ac14dc6ec6e8780215e5a6c5f64b3 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Tue, 6 Aug 2019 23:21:25 +0200 Subject: [PATCH 3/9] tmp(releaser/github): add test for create release --- internal/releaser/github/github.go | 10 +-- internal/releaser/github/github_test.go | 106 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 3f73b95..0d5d099 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -89,7 +89,7 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC prerelease := releaseVersion.Next.Version.Prerelease() != "" - release, resp, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{ + release, _, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{ TagName: &tag, TargetCommitish: &releaseVersion.Branch, Name: &generatedChangelog.Title, @@ -97,19 +97,19 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC Draft: &releaseVersion.Draft, Prerelease: &prerelease, }) - if err != nil { - if !strings.Contains(err.Error(), "already_exists") && resp.StatusCode >= http.StatusUnprocessableEntity { + if !strings.Contains(err.Error(), "already_exists") { return fmt.Errorf("could not create release: %v", err) + } else { + log.Infof("A release with tag %s already exits, will not perform a release or update", tag) } - log.Infof("A release with tag %s already exits, will not perform a release or update", tag) } else { g.release = release log.Debugf("Release repsone: %+v", *release) log.Infof("Crated release") } - return nil + } // UploadAssets uploads specified assets diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index c8bb1d3..d9aba70 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -2,10 +2,15 @@ package github_test import ( "fmt" + "net/http" + "net/http/httptest" "os" "testing" + "github.com/Masterminds/semver" + "github.com/Nightapes/go-semantic-release/internal/releaser/github" + "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" "github.com/stretchr/testify/assert" ) @@ -15,6 +20,15 @@ type testDouble struct { valid bool } +type testFourth struct { + config config.GitHubProvider + releaseVersion *shared.ReleaseVersion + generatedChangelog *shared.GeneratedChangelog + requestResponseBody string + requestResponseCode int + valid bool +} + var doublesNew = []testDouble{ testDouble{config: config.GitHubProvider{ Repo: "foo", @@ -55,6 +69,71 @@ var doublesValidateConfig = []testDouble{ }, } +var lastVersion, _ = semver.NewVersion("1.0.0") +var newVersion, _ = semver.NewVersion("2.0.0") + +var fourthsReleas = []testFourth{ + testFourth{ + config: config.GitHubProvider{ + Repo: "foo", + User: "bar", + }, + releaseVersion: &shared.ReleaseVersion{ + Last: shared.ReleaseVersionEntry{ + Version: lastVersion, + Commit: "foo", + }, + Next: shared.ReleaseVersionEntry{ + Version: newVersion, + Commit: "bar", + }, + Branch: "master", + Draft: false, + }, + generatedChangelog: &shared.GeneratedChangelog{ + Title: "title", + Content: "content", + }, + requestResponseBody: "{ \"url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1\", \"html_url\": \"https://github.com/octocat/Hello-World/releases/v1.0.0\", \"assets_url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1/assets\", \"upload_url\": \"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}\", \"tarball_url\": \"https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0\", \"zipball_url\": \"https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0\", \"id\": 1, \"node_id\": \"MDc6UmVsZWFzZTE=\", \"tag_name\": \"v1.0.0\", \"target_commitish\": \"master\", \"name\": \"v1.0.0\", \"body\": \"Description of the release\", \"draft\": false, \"prerelease\": false, \"created_at\": \"2013-02-27T19:35:32Z\", \"published_at\": \"2013-02-27T19:35:32Z\", \"author\": { \"login\": \"octocat\", \"id\": 1, \"node_id\": \"MDQ6VXNlcjE=\", \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/octocat\", \"html_url\": \"https://github.com/octocat\", \"followers_url\": \"https://api.github.com/users/octocat/followers\", \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\", \"organizations_url\": \"https://api.github.com/users/octocat/orgs\", \"repos_url\": \"https://api.github.com/users/octocat/repos\", \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/octocat/received_events\", \"type\": \"User\", \"site_admin\": false }, \"assets\": [ ]}", + requestResponseCode: 500, + valid: true, + }, + testFourth{ + config: config.GitHubProvider{ + Repo: "foo", + User: "bar", + }, + releaseVersion: &shared.ReleaseVersion{ + Last: shared.ReleaseVersionEntry{ + Version: lastVersion, + Commit: "foo", + }, + Next: shared.ReleaseVersionEntry{ + Version: newVersion, + Commit: "bar", + }, + Branch: "master", + Draft: false, + }, + generatedChangelog: &shared.GeneratedChangelog{ + Title: "title", + Content: "content", + }, + requestResponseBody: "", + requestResponseCode: 422, + valid: false, + }, +} + +func initHTTPServer(respCode int, body string) *httptest.Server { + + return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + rw.Write([]byte(body)) + rw.WriteHeader(500) + })) +} + func TestNew(t *testing.T) { for _, testOject := range doublesNew { if testOject.valid { @@ -116,3 +195,30 @@ func TestValidateConfig(t *testing.T) { } os.Unsetenv("GITHUB_ACCESS_TOKEN") } + +func TestCreateRelease(t *testing.T) { + os.Setenv("GITHUB_ACCESS_TOKEN", "XX") + + for _, testObejct := range fourthsReleas { + if testObejct.valid { + server := initHTTPServer(testObejct.requestResponseCode, "") + testObejct.config.CustomURL = server.URL + client, _ := github.New(&testObejct.config) + + err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) + assert.Equal(t, testObejct.valid, err == nil) + + } else { + testObejct.config.CustomURL = "foo" + client, _ := github.New(&testObejct.config) + + err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) + if err != nil { + t.Log(err) + } + assert.Equal(t, testObejct.valid, err == nil) + } + } + os.Unsetenv("GITHUB_ACCESS_TOKEN") + +} From 3c500142aa701eaa40cd7b3b1a2018cf963ae5ec Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Wed, 7 Aug 2019 23:11:04 +0200 Subject: [PATCH 4/9] temp(releaser/github): modify test --- internal/releaser/github/github_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index d9aba70..8afde31 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -130,7 +130,9 @@ func initHTTPServer(respCode int, body string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Write([]byte(body)) - rw.WriteHeader(500) + rw.Header().Set("Content-Type", "application/json") + + rw.WriteHeader(respCode) })) } From c086b12f01364e5db36543848a3aab2bad582a29 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Wed, 7 Aug 2019 23:24:13 +0200 Subject: [PATCH 5/9] feat(releaser): add gitlab as relase option --- internal/releaser/gitlab/gitlab.go | 225 ++++++++++++++++++++++++ internal/releaser/gitlab/types.go | 25 +++ internal/releaser/releaser.go | 4 + internal/releaser/util/util.go | 81 +++++++++ pkg/config/config.go | 8 + pkg/semanticrelease/semantic-release.go | 4 +- 6 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 internal/releaser/gitlab/gitlab.go create mode 100644 internal/releaser/gitlab/types.go diff --git a/internal/releaser/gitlab/gitlab.go b/internal/releaser/gitlab/gitlab.go new file mode 100644 index 0000000..b739232 --- /dev/null +++ b/internal/releaser/gitlab/gitlab.go @@ -0,0 +1,225 @@ +package gitlab + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" + "strings" + "time" + + "github.com/Nightapes/go-semantic-release/internal/releaser/util" + "github.com/Nightapes/go-semantic-release/internal/shared" + "github.com/Nightapes/go-semantic-release/pkg/config" + + log "github.com/sirupsen/logrus" +) + +// GITLAB identifer for gitlab interface +const GITLAB = "gitlab" + +// Client type struct +type Client struct { + config *config.GitLabProvider + context context.Context + client *http.Client + baseURL string + apiURL string + token string + release string +} + +// New initialize a new gitlabRelease +func New(config *config.GitLabProvider) (*Client, error) { + accessToken, err := util.GetAccessToken(GITLAB) + if err != nil { + return nil, err + } + + if config.CustomURL == "" { + config.CustomURL = "https://gitlab.com" + } + + baseURL, err := util.CheckURL(config.CustomURL) + log.Debugf("Use gitlab url %s", baseURL) + if err != nil { + return nil, err + } + + ctx := context.Background() + tokenHeader := util.NewAddHeaderTransport(nil, "PRIVATE-TOKEN", accessToken) + acceptHeader := util.NewAddHeaderTransport(tokenHeader, "Accept", "application/json") + httpClient := &http.Client{ + Transport: acceptHeader, + Timeout: time.Second * 60, + } + + return &Client{ + token: accessToken, + config: config, + context: ctx, + baseURL: baseURL, + apiURL: baseURL + "api/v4", + client: httpClient, + }, nil +} + +//GetCommitURL for gitlab +func (g *Client) GetCommitURL() string { + return fmt.Sprintf("%s%s/commit/{{hash}}", g.baseURL, g.config.Repo) +} + +//GetCompareURL for gitlab +func (g *Client) GetCompareURL(oldVersion, newVersion string) string { + return fmt.Sprintf("%s%s/compare/%s...%s", g.baseURL, g.config.Repo, oldVersion, newVersion) +} + +//ValidateConfig for gitlab +func (g *Client) ValidateConfig() error { + log.Debugf("validate gitlab provider config") + + if g.config.Repo == "" { + return fmt.Errorf("gitlab Repro is not set") + } + + return nil + +} + +// CreateRelease creates release on remote +func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { + + tag := releaseVersion.Next.Version.String() + g.release = tag + log.Debugf("create release with version %s", tag) + + bodyBytes, err := json.Marshal(Release{ + TagName: tag, + Name: generatedChangelog.Title, + Description: generatedChangelog.Content, + Ref: releaseVersion.Branch, + }) + if err != nil { + return err + } + bodyReader := bytes.NewReader(bodyBytes) + + url := fmt.Sprintf("%s/projects/%s/releases", g.apiURL, util.PathEscape(g.config.Repo)) + log.Debugf("Send release to %s", url) + + resp, err := g.client.Post(url, "application/json", bodyReader) + + if err != nil { + + if !strings.Contains(err.Error(), "already_exists") && resp.StatusCode >= http.StatusUnprocessableEntity { + return fmt.Errorf("could not create release: %v", err) + } + log.Infof("A release with tag %s already exits, will not perform a release or update", tag) + } else { + + defer resp.Body.Close() + + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + log.Debugf("Release repsone: %+v", string(bodyBytes)) + + if err := util.IsValidResult(resp); err != nil { + return err + } + + log.Infof("Crated release") + } + + return nil +} + +// UploadAssets uploads specified assets +func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { + filesToUpload, err := util.PrepareAssets(repoDir, assets) + if err != nil { + return err + } + for _, f := range filesToUpload { + + file, err := os.Open(*f) + defer file.Close() + if err != nil { + return err + } + fileInfo, _ := file.Stat() + + result, err := g.uploadFile(fileInfo.Name(), file) + if err != nil { + return fmt.Errorf("releaser: gitlab: Could not upload asset %s: %s", file.Name(), err.Error()) + } + + downloadURL := fmt.Sprintf("%s%s%s", g.baseURL, g.config.Repo, result.URL) + + log.Infof("Uploaded file %s to gitlab can be downloaded under %s", file.Name(), downloadURL) + + path := fmt.Sprintf("%s/projects/%s/releases/%s/assets/links?name=%s&url=%s", g.apiURL, util.PathEscape(g.config.Repo), g.release, util.PathEscape(fileInfo.Name()), downloadURL) + + req, err := http.NewRequest("POST", path, nil) + if err != nil { + return err + } + + log.Infof("Link file %s with release %s", file.Name(), g.release) + + resp, err := util.Do(g.client, req, nil) + if err != nil { + return err + } + + if err = util.IsValidResult(resp); err != nil { + return err + } + + log.Infof("Link file with release %s done", g.release) + } + return nil +} + +func (g *Client) uploadFile(fileName string, file *os.File) (*ProjectFile, error) { + + b := &bytes.Buffer{} + w := multipart.NewWriter(b) + + fw, err := w.CreateFormFile("file", fileName) + if err != nil { + return nil, err + } + + _, err = io.Copy(fw, file) + if err != nil { + return nil, err + } + w.Close() + + url := fmt.Sprintf("%s/projects/%s/uploads", g.apiURL, util.PathEscape(g.config.Repo)) + + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return nil, err + } + + req.Body = ioutil.NopCloser(b) + req.ContentLength = int64(b.Len()) + req.Header.Set("Content-Type", w.FormDataContentType()) + + uf := &ProjectFile{} + resp, err := util.Do(g.client, req, uf) + + if err = util.IsValidResult(resp); err != nil { + return nil, err + } + + return uf, nil +} diff --git a/internal/releaser/gitlab/types.go b/internal/releaser/gitlab/types.go new file mode 100644 index 0000000..c5c092b --- /dev/null +++ b/internal/releaser/gitlab/types.go @@ -0,0 +1,25 @@ +package gitlab + +// Release struct +type Release struct { + TagName string `json:"tag_name"` + Name string `json:"name"` + Ref string `json:"ref"` + Description string `json:"description,omitempty"` + Assets struct { + Links []*ReleaseLink `json:"links"` + } `json:"assets"` +} + +// ReleaseLink struct +type ReleaseLink struct { + Name string `json:"name"` + URL string `json:"url"` +} + +// ProjectFile struct +type ProjectFile struct { + Alt string `json:"alt"` + URL string `json:"url"` + Markdown string `json:"markdown"` +} diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go index 5d82da3..b2a9863 100644 --- a/internal/releaser/releaser.go +++ b/internal/releaser/releaser.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/Nightapes/go-semantic-release/internal/releaser/github" + "github.com/Nightapes/go-semantic-release/internal/releaser/gitlab" "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" @@ -37,6 +38,9 @@ func (r *Releasers) GetReleaser() (Releaser, error) { case github.GITHUB: log.Debugf("initialize new %s-provider", github.GITHUB) return github.New(&r.config.GitHubProvider) + case gitlab.GITLAB: + log.Debugf("initialize new %s-provider", gitlab.GITLAB) + return gitlab.New(&r.config.GitLabProvider) } return nil, fmt.Errorf("could not initialize a releaser from this type: %s", r.config.Release) } diff --git a/internal/releaser/util/util.go b/internal/releaser/util/util.go index 86745e1..b535df2 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -3,9 +3,11 @@ package util import ( "archive/zip" "context" + "encoding/json" "fmt" "io" "net/http" + "net/url" "os" "strings" @@ -25,6 +27,27 @@ func CreateBearerHTTPClient(ctx context.Context, token string) *http.Client { return client } +// AddHeaderTransport struct +type AddHeaderTransport struct { + T http.RoundTripper + key string + value string +} + +// RoundTrip add header +func (adt *AddHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Add(adt.key, adt.value) + return adt.T.RoundTrip(req) +} + +//NewAddHeaderTransport to add default header +func NewAddHeaderTransport(T http.RoundTripper, key, value string) *AddHeaderTransport { + if T == nil { + T = http.DefaultTransport + } + return &AddHeaderTransport{T, key, value} +} + // GetAccessToken lookup for the providers accesstoken func GetAccessToken(providerName string) (string, error) { var token string @@ -109,3 +132,61 @@ func zipFile(repository string, file string) (string, error) { return zipFileName, nil } + +// CheckURL if is valid +func CheckURL(urlStr string) (string, error) { + + if !strings.HasSuffix(urlStr, "/") { + urlStr += "/" + } + + _, err := url.Parse(urlStr) + if err != nil { + return "", err + } + + return urlStr, nil +} + +//PathEscape to be url save +func PathEscape(s string) string { + return strings.Replace(url.PathEscape(s), ".", "%2E", -1) +} + +// Do request for client +func Do(client *http.Client, req *http.Request, v interface{}) (*http.Response, error) { + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case 200, 201, 202, 204, 304: + if v != nil { + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(v) + } + } + } + + return resp, err +} + +func IsValidResult(resp *http.Response) error { + switch resp.StatusCode { + case 200, 201, 202, 204, 304: + return nil + default: + return fmt.Errorf("%s %s: %d", resp.Request.Method, resp.Request.URL, resp.StatusCode) + } +} + +func ShouldRetry(resp *http.Response) bool { + if resp.StatusCode == http.StatusTooManyRequests { + return true + } + return false +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 7c08d77..31936d0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -29,6 +29,13 @@ type GitHubProvider struct { AccessToken string } +// GitLabProvider struct +type GitLabProvider struct { + Repo string `yaml:"repo"` + CustomURL string `yaml:"customUrl,omitempty"` + AccessToken string +} + // ReleaseConfig struct type ReleaseConfig struct { CommitFormat string `yaml:"commitFormat"` @@ -36,6 +43,7 @@ type ReleaseConfig struct { Changelog ChangelogConfig `yaml:"changelog,omitempty"` Release string `yaml:"release,omitempty"` GitHubProvider GitHubProvider `yaml:"github,omitempty"` + GitLabProvider GitLabProvider `yaml:"gitlab,omitempty"` Assets []Asset `yaml:"assets"` ReleaseTitle string `yaml:"title"` IsPreRelease, IsDraft bool diff --git a/pkg/semanticrelease/semantic-release.go b/pkg/semanticrelease/semantic-release.go index 4d90658..cd3e2b0 100644 --- a/pkg/semanticrelease/semantic-release.go +++ b/pkg/semanticrelease/semantic-release.go @@ -193,8 +193,8 @@ func (s *SemanticRelease) Release(provider *ci.ProviderConfig, force bool) error return err } - if releaseVersion.Next.Version.Equal(releaseVersion.Next.Version) { - log.Infof("No new version, no release needed") + if releaseVersion.Next.Version.Equal(releaseVersion.Last.Version) { + log.Infof("No new version, no release needed %s <> %s", releaseVersion.Next.Version.String(), releaseVersion.Last.Version.String()) return nil } From 0037abfaf75968d9a56b4a7e579b7f084fdb0b75 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Thu, 8 Aug 2019 20:07:22 +0200 Subject: [PATCH 6/9] test(internal/releaser/github): add test for CreateRelease() --- internal/releaser/github/github_test.go | 68 +++++++++++++------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index 8afde31..0925a2a 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -7,6 +7,8 @@ import ( "os" "testing" + log "github.com/sirupsen/logrus" + "github.com/Masterminds/semver" "github.com/Nightapes/go-semantic-release/internal/releaser/github" @@ -15,12 +17,12 @@ import ( "github.com/stretchr/testify/assert" ) -type testDouble struct { +type testHelperMethodStruct struct { config config.GitHubProvider valid bool } -type testFourth struct { +type testReleaseStruct struct { config config.GitHubProvider releaseVersion *shared.ReleaseVersion generatedChangelog *shared.GeneratedChangelog @@ -29,15 +31,15 @@ type testFourth struct { valid bool } -var doublesNew = []testDouble{ - testDouble{config: config.GitHubProvider{ +var testNewClient = []testHelperMethodStruct{ + testHelperMethodStruct{config: config.GitHubProvider{ Repo: "foo", User: "bar", }, valid: true, }, - testDouble{config: config.GitHubProvider{ + testHelperMethodStruct{config: config.GitHubProvider{ Repo: "foo", User: "bar", CustomURL: "https://test.com", @@ -46,22 +48,22 @@ var doublesNew = []testDouble{ }, } -var doublesValidateConfig = []testDouble{ - testDouble{config: config.GitHubProvider{ +var testHelperMethod = []testHelperMethodStruct{ + testHelperMethodStruct{config: config.GitHubProvider{ Repo: "foo", User: "bar", }, valid: true, }, - testDouble{config: config.GitHubProvider{ + testHelperMethodStruct{config: config.GitHubProvider{ Repo: "", User: "bar", }, valid: false, }, - testDouble{config: config.GitHubProvider{ + testHelperMethodStruct{config: config.GitHubProvider{ Repo: "foo", User: "", }, @@ -72,8 +74,8 @@ var doublesValidateConfig = []testDouble{ var lastVersion, _ = semver.NewVersion("1.0.0") var newVersion, _ = semver.NewVersion("2.0.0") -var fourthsReleas = []testFourth{ - testFourth{ +var testReleases = []testReleaseStruct{ + testReleaseStruct{ config: config.GitHubProvider{ Repo: "foo", User: "bar", @@ -94,11 +96,10 @@ var fourthsReleas = []testFourth{ Title: "title", Content: "content", }, - requestResponseBody: "{ \"url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1\", \"html_url\": \"https://github.com/octocat/Hello-World/releases/v1.0.0\", \"assets_url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1/assets\", \"upload_url\": \"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}\", \"tarball_url\": \"https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0\", \"zipball_url\": \"https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0\", \"id\": 1, \"node_id\": \"MDc6UmVsZWFzZTE=\", \"tag_name\": \"v1.0.0\", \"target_commitish\": \"master\", \"name\": \"v1.0.0\", \"body\": \"Description of the release\", \"draft\": false, \"prerelease\": false, \"created_at\": \"2013-02-27T19:35:32Z\", \"published_at\": \"2013-02-27T19:35:32Z\", \"author\": { \"login\": \"octocat\", \"id\": 1, \"node_id\": \"MDQ6VXNlcjE=\", \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/octocat\", \"html_url\": \"https://github.com/octocat\", \"followers_url\": \"https://api.github.com/users/octocat/followers\", \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\", \"organizations_url\": \"https://api.github.com/users/octocat/orgs\", \"repos_url\": \"https://api.github.com/users/octocat/repos\", \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/octocat/received_events\", \"type\": \"User\", \"site_admin\": false }, \"assets\": [ ]}", - requestResponseCode: 500, + requestResponseCode: 200, valid: true, }, - testFourth{ + testReleaseStruct{ config: config.GitHubProvider{ Repo: "foo", User: "bar", @@ -119,8 +120,7 @@ var fourthsReleas = []testFourth{ Title: "title", Content: "content", }, - requestResponseBody: "", - requestResponseCode: 422, + requestResponseCode: 400, valid: false, }, } @@ -128,16 +128,17 @@ var fourthsReleas = []testFourth{ func initHTTPServer(respCode int, body string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - - rw.Write([]byte(body)) - rw.Header().Set("Content-Type", "application/json") - + log.Infof("Got request with method: %s, URL: %s", req.Method, req.URL) + log.Infof("Will response with statuscode: %d, body: %s", respCode, body) rw.WriteHeader(respCode) + rw.Header().Set("Content-Type", "application/json") + rw.Write([]byte(body)) + })) } func TestNew(t *testing.T) { - for _, testOject := range doublesNew { + for _, testOject := range testNewClient { if testOject.valid { os.Setenv("GITHUB_ACCESS_TOKEN", "XXX") } @@ -152,7 +153,7 @@ func TestNew(t *testing.T) { func TestGetCommitURL(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") - for _, testOject := range doublesNew { + for _, testOject := range testNewClient { client, _ := github.New(&testOject.config) actualUrl := client.GetCommitURL() if testOject.config.CustomURL != "" { @@ -170,7 +171,7 @@ func TestGetCommitURL(t *testing.T) { func TestGetCompareURL(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") - for _, testOject := range doublesNew { + for _, testOject := range testNewClient { client, _ := github.New(&testOject.config) actualUrl := client.GetCompareURL("1", "2") if testOject.config.CustomURL != "" { @@ -188,7 +189,7 @@ func TestGetCompareURL(t *testing.T) { func TestValidateConfig(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") - for _, testOject := range doublesValidateConfig { + for _, testOject := range testHelperMethod { client, _ := github.New(&testOject.config) err := client.ValidateConfig() @@ -201,24 +202,25 @@ func TestValidateConfig(t *testing.T) { func TestCreateRelease(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") - for _, testObejct := range fourthsReleas { + for _, testObejct := range testReleases { if testObejct.valid { server := initHTTPServer(testObejct.requestResponseCode, "") testObejct.config.CustomURL = server.URL client, _ := github.New(&testObejct.config) - err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) - assert.Equal(t, testObejct.valid, err == nil) - - } else { - testObejct.config.CustomURL = "foo" - client, _ := github.New(&testObejct.config) - err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) if err != nil { t.Log(err) } - assert.Equal(t, testObejct.valid, err == nil) + assert.NoError(t, err) + server.Close() + } else { + testObejct.config.CustomURL = "http://foo" + client, _ := github.New(&testObejct.config) + + err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) + t.Log(err) + assert.Error(t, err) } } os.Unsetenv("GITHUB_ACCESS_TOKEN") From 3bf84d5ad12fc4f448b6dad2f7aa4210c178c08f Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 20:17:14 +0200 Subject: [PATCH 7/9] test(github): refactor unit test --- internal/releaser/github/github.go | 3 +-- internal/releaser/github/github_test.go | 31 ++++++++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 0d5d099..8750251 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -100,9 +100,8 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC if err != nil { if !strings.Contains(err.Error(), "already_exists") { return fmt.Errorf("could not create release: %v", err) - } else { - log.Infof("A release with tag %s already exits, will not perform a release or update", tag) } + log.Infof("A release with tag %s already exits, will not perform a release or update", tag) } else { g.release = release log.Debugf("Release repsone: %+v", *release) diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index 8afde31..c06e0ca 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -12,6 +12,7 @@ import ( "github.com/Nightapes/go-semantic-release/internal/releaser/github" "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" + log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -95,7 +96,7 @@ var fourthsReleas = []testFourth{ Content: "content", }, requestResponseBody: "{ \"url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1\", \"html_url\": \"https://github.com/octocat/Hello-World/releases/v1.0.0\", \"assets_url\": \"https://api.github.com/repos/octocat/Hello-World/releases/1/assets\", \"upload_url\": \"https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}\", \"tarball_url\": \"https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0\", \"zipball_url\": \"https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0\", \"id\": 1, \"node_id\": \"MDc6UmVsZWFzZTE=\", \"tag_name\": \"v1.0.0\", \"target_commitish\": \"master\", \"name\": \"v1.0.0\", \"body\": \"Description of the release\", \"draft\": false, \"prerelease\": false, \"created_at\": \"2013-02-27T19:35:32Z\", \"published_at\": \"2013-02-27T19:35:32Z\", \"author\": { \"login\": \"octocat\", \"id\": 1, \"node_id\": \"MDQ6VXNlcjE=\", \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/octocat\", \"html_url\": \"https://github.com/octocat\", \"followers_url\": \"https://api.github.com/users/octocat/followers\", \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\", \"organizations_url\": \"https://api.github.com/users/octocat/orgs\", \"repos_url\": \"https://api.github.com/users/octocat/repos\", \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/octocat/received_events\", \"type\": \"User\", \"site_admin\": false }, \"assets\": [ ]}", - requestResponseCode: 500, + requestResponseCode: 200, valid: true, }, testFourth{ @@ -129,10 +130,13 @@ func initHTTPServer(respCode int, body string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.Write([]byte(body)) - rw.Header().Set("Content-Type", "application/json") + log.Infof("Got call from %s %s", req.Method, req.URL.String()) rw.WriteHeader(respCode) + rw.Header().Set("Content-Type", "application/json") + + rw.Write([]byte(body)) + })) } @@ -203,22 +207,27 @@ func TestCreateRelease(t *testing.T) { for _, testObejct := range fourthsReleas { if testObejct.valid { - server := initHTTPServer(testObejct.requestResponseCode, "") + server := initHTTPServer(testObejct.requestResponseCode, testObejct.requestResponseBody) testObejct.config.CustomURL = server.URL client, _ := github.New(&testObejct.config) - err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) - assert.Equal(t, testObejct.valid, err == nil) - - } else { - testObejct.config.CustomURL = "foo" - client, _ := github.New(&testObejct.config) - err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) if err != nil { t.Log(err) } assert.Equal(t, testObejct.valid, err == nil) + + server.Close() + + } else { + testObejct.config.CustomURL = "http://foo" + client, _ := github.New(&testObejct.config) + + err := client.CreateRelease(testObejct.releaseVersion, testObejct.generatedChangelog) + if err != nil { + t.Log(err) + } + assert.Error(t, err) } } os.Unsetenv("GITHUB_ACCESS_TOKEN") From ff82ec7acd295a472be78d6242ab21d4136233e2 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Thu, 8 Aug 2019 20:44:33 +0200 Subject: [PATCH 8/9] chore(util/github-release): fix golint issues --- internal/releaser/github/github.go | 14 +++++++------- internal/releaser/util/util.go | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 8750251..0eb9b4d 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -98,15 +98,15 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC Prerelease: &prerelease, }) if err != nil { - if !strings.Contains(err.Error(), "already_exists") { - return fmt.Errorf("could not create release: %v", err) + if strings.Contains(err.Error(), "already_exists") { + log.Infof("A release with tag %s already exits, will not perform a release or update", tag) + return nil } - log.Infof("A release with tag %s already exits, will not perform a release or update", tag) - } else { - g.release = release - log.Debugf("Release repsone: %+v", *release) - log.Infof("Crated release") + return fmt.Errorf("could not create release: %v", err) } + g.release = release + log.Debugf("Release repsone: %+v", *release) + log.Infof("Crated release") return nil } diff --git a/internal/releaser/util/util.go b/internal/releaser/util/util.go index b535df2..a1a476a 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -175,6 +175,7 @@ func Do(client *http.Client, req *http.Request, v interface{}) (*http.Response, return resp, err } +// IsValidResult validates response code func IsValidResult(resp *http.Response) error { switch resp.StatusCode { case 200, 201, 202, 204, 304: @@ -184,6 +185,7 @@ func IsValidResult(resp *http.Response) error { } } +// ShouldRetry request func ShouldRetry(resp *http.Response) bool { if resp.StatusCode == http.StatusTooManyRequests { return true From c9d942003787132a9ee4b2d7fd5b1ccbf44f13b8 Mon Sep 17 00:00:00 2001 From: fwiedmann Date: Thu, 8 Aug 2019 21:01:40 +0200 Subject: [PATCH 9/9] chore(gitlab/github/util): fix golangci-lint issues --- internal/releaser/github/github_test.go | 24 +++++++++++++----------- internal/releaser/gitlab/gitlab.go | 6 +++++- internal/releaser/util/util.go | 5 +---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/internal/releaser/github/github_test.go b/internal/releaser/github/github_test.go index 93be9dc..d9ab789 100644 --- a/internal/releaser/github/github_test.go +++ b/internal/releaser/github/github_test.go @@ -135,7 +135,9 @@ func initHTTPServer(respCode int, body string) *httptest.Server { rw.WriteHeader(respCode) rw.Header().Set("Content-Type", "application/json") - rw.Write([]byte(body)) + if _, err := rw.Write([]byte(body)); err != nil { + log.Info(err) + } })) } @@ -158,14 +160,14 @@ func TestGetCommitURL(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") for _, testOject := range testNewClient { client, _ := github.New(&testOject.config) - actualUrl := client.GetCommitURL() + actualURL := client.GetCommitURL() if testOject.config.CustomURL != "" { - expectedUrl := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo) - assert.EqualValues(t, expectedUrl, actualUrl) + expectedURL := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo) + assert.EqualValues(t, expectedURL, actualURL) } else { - expectedUrl := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", "https://github.com", testOject.config.User, testOject.config.Repo) - assert.EqualValues(t, expectedUrl, actualUrl) + expectedURL := fmt.Sprintf("%s/%s/%s/commit/{{hash}}", "https://github.com", testOject.config.User, testOject.config.Repo) + assert.EqualValues(t, expectedURL, actualURL) } } os.Unsetenv("GITHUB_ACCESS_TOKEN") @@ -176,14 +178,14 @@ func TestGetCompareURL(t *testing.T) { os.Setenv("GITHUB_ACCESS_TOKEN", "XX") for _, testOject := range testNewClient { client, _ := github.New(&testOject.config) - actualUrl := client.GetCompareURL("1", "2") + actualURL := client.GetCompareURL("1", "2") if testOject.config.CustomURL != "" { - expectedUrl := fmt.Sprintf("%s/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2") - assert.EqualValues(t, expectedUrl, actualUrl) + expectedURL := fmt.Sprintf("%s/%s/%s/compare/%s...%s", testOject.config.CustomURL, testOject.config.User, testOject.config.Repo, "1", "2") + assert.EqualValues(t, expectedURL, actualURL) } else { - expectedUrl := fmt.Sprintf("%s/%s/%s/compare/%s...%s", "https://github.com", testOject.config.User, testOject.config.Repo, "1", "2") - assert.EqualValues(t, expectedUrl, actualUrl) + expectedURL := fmt.Sprintf("%s/%s/%s/compare/%s...%s", "https://github.com", testOject.config.User, testOject.config.Repo, "1", "2") + assert.EqualValues(t, expectedURL, actualURL) } } os.Unsetenv("GITHUB_ACCESS_TOKEN") diff --git a/internal/releaser/gitlab/gitlab.go b/internal/releaser/gitlab/gitlab.go index b739232..d966c11 100644 --- a/internal/releaser/gitlab/gitlab.go +++ b/internal/releaser/gitlab/gitlab.go @@ -149,10 +149,11 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { for _, f := range filesToUpload { file, err := os.Open(*f) - defer file.Close() if err != nil { return err } + defer file.Close() + fileInfo, _ := file.Stat() result, err := g.uploadFile(fileInfo.Name(), file) @@ -216,6 +217,9 @@ func (g *Client) uploadFile(fileName string, file *os.File) (*ProjectFile, error uf := &ProjectFile{} resp, err := util.Do(g.client, req, uf) + if err != nil { + return nil, err + } if err = util.IsValidResult(resp); err != nil { return nil, err diff --git a/internal/releaser/util/util.go b/internal/releaser/util/util.go index a1a476a..61523fc 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -187,8 +187,5 @@ func IsValidResult(resp *http.Response) error { // ShouldRetry request func ShouldRetry(resp *http.Response) bool { - if resp.StatusCode == http.StatusTooManyRequests { - return true - } - return false + return resp.StatusCode == http.StatusTooManyRequests }