From 2f20d8c31cccc1a9ab0e8cc646f93460d5040ff9 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:30:29 +0200 Subject: [PATCH 1/8] refactor(releaser): clean up code --- .release.yml | 12 +++-- .travis.yml | 6 +-- go.mod | 1 - go.sum | 1 + internal/releaser/github/github.go | 12 +++-- internal/releaser/gitlab/gitlab.go | 83 ++++++++++++++++-------------- internal/releaser/util/util.go | 5 +- 7 files changed, 62 insertions(+), 58 deletions(-) diff --git a/.release.yml b/.release.yml index f505632..1d2b869 100644 --- a/.release.yml +++ b/.release.yml @@ -5,18 +5,20 @@ branch: rc: rc beta: beta alpha: alpha - add_git_releases: alpha changelog: printAll: false template: '' templatePath: '' -release: 'github' +release: 'gitlab' assets: - - name: ./build/go-semantic-release - compress: false - - name: ./build/go-semantic-release.exe + - name: go-semantic-release compress: false + - name: go-semantic-release.exe + compress: true github: repo: "go-semantic-release" user: "nightapes" customUrl: "" +gitlab: + repo: "test/bla" + customUrl: "http://gitlab.example.com" diff --git a/.travis.yml b/.travis.yml index f2cd291..34e9ede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,11 +29,11 @@ script: - go test -v ./... - go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/ - echo "Building version `./build/go-semantic-release-temp next --loglevel debug --no-cache`" - - go build -o build/go-semantic-release -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ - - GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ + - go build -o build/go-semantic-release -ldflags "-w -s --X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ + - GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ after_success: - - ./build/go-semantic-release-temp release --loglevel debug + - ./build/go-semantic-release-temp release --loglevel trace branches: except: diff --git a/go.mod b/go.mod index d1e9cef..6d9f669 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.12 require ( github.com/Masterminds/semver v1.4.2 - github.com/coreos/etcd v3.3.10+incompatible github.com/gliderlabs/ssh v0.2.2 // indirect github.com/google/go-cmp v0.3.0 // indirect github.com/google/go-github/v25 v25.1.3 diff --git a/go.sum b/go.sum index 5a9a17d..8741862 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 3f73b95..f5bddc4 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -25,6 +25,7 @@ type Client struct { context context.Context release *github.RepositoryRelease baseURL string + log *log.Entry } // New initialize a new GitHubRelease @@ -52,6 +53,7 @@ func New(c *config.GitHubProvider) (*Client, error) { client: client, context: ctx, baseURL: baseURL, + log: log.WithField("releaser", GITHUB), }, err } @@ -85,7 +87,7 @@ func (g *Client) ValidateConfig() error { func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { tag := releaseVersion.Next.Version.String() - log.Debugf("create release with version %s", tag) + g.log.Debugf("create release with version %s", tag) prerelease := releaseVersion.Next.Version.Prerelease() != "" @@ -102,11 +104,11 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC 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) + g.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") + g.log.Debugf("Release repsone: %+v", *release) + g.log.Infof("Crated release") } return nil @@ -133,7 +135,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { } if resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("releaser: github: Could not upload asset %s: %s", file.Name(), resp.Status) + return fmt.Errorf("could not upload asset %s: %s", file.Name(), resp.Status) } } } diff --git a/internal/releaser/gitlab/gitlab.go b/internal/releaser/gitlab/gitlab.go index b739232..173f18e 100644 --- a/internal/releaser/gitlab/gitlab.go +++ b/internal/releaser/gitlab/gitlab.go @@ -32,6 +32,7 @@ type Client struct { apiURL string token string release string + log *log.Entry } // New initialize a new gitlabRelease @@ -41,16 +42,6 @@ func New(config *config.GitLabProvider) (*Client, error) { 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") @@ -63,9 +54,10 @@ func New(config *config.GitLabProvider) (*Client, error) { token: accessToken, config: config, context: ctx, - baseURL: baseURL, - apiURL: baseURL + "api/v4", + baseURL: config.CustomURL, + apiURL: config.CustomURL + "api/v4", client: httpClient, + log: log.WithField("releaser", GITLAB), }, nil } @@ -81,12 +73,21 @@ func (g *Client) GetCompareURL(oldVersion, newVersion string) string { //ValidateConfig for gitlab func (g *Client) ValidateConfig() error { - log.Debugf("validate gitlab provider config") + g.log.Debugf("validate gitlab provider config") if g.config.Repo == "" { return fmt.Errorf("gitlab Repro is not set") } + g.config.Repo = strings.Trim(g.config.Repo, "/") + + if g.config.CustomURL == "" { + g.config.CustomURL = "https://gitlab.com" + } + + g.config.CustomURL = strings.Trim(g.config.CustomURL, "/") + g.log.Debugf("Use gitlab url %s", g.config.CustomURL) + return nil } @@ -96,7 +97,9 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC tag := releaseVersion.Next.Version.String() g.release = tag - log.Debugf("create release with version %s", tag) + g.log.Debugf("create release with version %s", tag) + url := fmt.Sprintf("%s/projects/%s/releases", g.apiURL, util.PathEscape(g.config.Repo)) + g.log.Debugf("Send release to %s", url) bodyBytes, err := json.Marshal(Release{ TagName: tag, @@ -107,36 +110,32 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC 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) + req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) + if err != nil { + return err + } - resp, err := g.client.Post(url, "application/json", bodyReader) + req.Header.Set("Content-Type", "application/json") + resp, err := util.Do(g.client, req, nil) 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 fmt.Errorf("could not create release: %s", err.Error()) } + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + log.Debugf("Release repsone: %+v", string(respBodyBytes)) + + if err := util.IsValidResult(resp); err != nil { + return err + } + + log.Infof("Crated release") + return nil } @@ -149,15 +148,16 @@ 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) if err != nil { - return fmt.Errorf("releaser: gitlab: Could not upload asset %s: %s", file.Name(), err.Error()) + return fmt.Errorf("could not upload asset %s: %s", file.Name(), err.Error()) } downloadURL := fmt.Sprintf("%s%s%s", g.baseURL, g.config.Repo, result.URL) @@ -182,7 +182,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { return err } - log.Infof("Link file with release %s done", g.release) + log.Infof("Link file with release %s is done", g.release) } return nil } @@ -216,6 +216,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 b535df2..7956824 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -185,8 +185,5 @@ func IsValidResult(resp *http.Response) error { } func ShouldRetry(resp *http.Response) bool { - if resp.StatusCode == http.StatusTooManyRequests { - return true - } - return false + return resp.StatusCode == http.StatusTooManyRequests } From 42a6a5fcf05fc81be259a6bf3bfc30b69bdbadf7 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:31:30 +0200 Subject: [PATCH 2/8] build(releaser): fix release config --- .release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.release.yml b/.release.yml index 1d2b869..608032e 100644 --- a/.release.yml +++ b/.release.yml @@ -9,7 +9,7 @@ changelog: printAll: false template: '' templatePath: '' -release: 'gitlab' +release: 'github' assets: - name: go-semantic-release compress: false @@ -19,6 +19,3 @@ github: repo: "go-semantic-release" user: "nightapes" customUrl: "" -gitlab: - repo: "test/bla" - customUrl: "http://gitlab.example.com" From fab3fc030e223c39c5dae8d12a518f5b0c15b63d Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:31:30 +0200 Subject: [PATCH 3/8] build(releaser): fix release config --- .release.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.release.yml b/.release.yml index 1d2b869..4f1a577 100644 --- a/.release.yml +++ b/.release.yml @@ -9,16 +9,13 @@ changelog: printAll: false template: '' templatePath: '' -release: 'gitlab' +release: 'github' assets: - - name: go-semantic-release + - name: ./build/go-semantic-release + compress: false + - name: ./build/go-semantic-release.exe compress: false - - name: go-semantic-release.exe - compress: true github: repo: "go-semantic-release" user: "nightapes" customUrl: "" -gitlab: - repo: "test/bla" - customUrl: "http://gitlab.example.com" From 92b42c8ffaf9428be818bc5ad85626c129dbd711 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Wed, 7 Aug 2019 23:24:13 +0200 Subject: [PATCH 4/8] 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 5791d3b41c9a1197ae55c9536f78d26c11a07e8f Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:30:29 +0200 Subject: [PATCH 5/8] refactor(releaser): clean up code --- .release.yml | 12 +++-- .travis.yml | 6 +-- go.mod | 1 - go.sum | 1 + internal/releaser/github/github.go | 12 +++-- internal/releaser/gitlab/gitlab.go | 83 ++++++++++++++++-------------- internal/releaser/util/util.go | 5 +- 7 files changed, 62 insertions(+), 58 deletions(-) diff --git a/.release.yml b/.release.yml index f505632..1d2b869 100644 --- a/.release.yml +++ b/.release.yml @@ -5,18 +5,20 @@ branch: rc: rc beta: beta alpha: alpha - add_git_releases: alpha changelog: printAll: false template: '' templatePath: '' -release: 'github' +release: 'gitlab' assets: - - name: ./build/go-semantic-release - compress: false - - name: ./build/go-semantic-release.exe + - name: go-semantic-release compress: false + - name: go-semantic-release.exe + compress: true github: repo: "go-semantic-release" user: "nightapes" customUrl: "" +gitlab: + repo: "test/bla" + customUrl: "http://gitlab.example.com" diff --git a/.travis.yml b/.travis.yml index f2cd291..34e9ede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,11 +29,11 @@ script: - go test -v ./... - go build -o build/go-semantic-release-temp ./cmd/go-semantic-release/ - echo "Building version `./build/go-semantic-release-temp next --loglevel debug --no-cache`" - - go build -o build/go-semantic-release -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ - - GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ + - go build -o build/go-semantic-release -ldflags "-w -s --X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ + - GOOS=windows GOARCH=386 go build -o build/go-semantic-release.exe -ldflags "-w -s -X main.version=`./build/go-semantic-release-temp next`" ./cmd/go-semantic-release/ after_success: - - ./build/go-semantic-release-temp release --loglevel debug + - ./build/go-semantic-release-temp release --loglevel trace branches: except: diff --git a/go.mod b/go.mod index d1e9cef..6d9f669 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.12 require ( github.com/Masterminds/semver v1.4.2 - github.com/coreos/etcd v3.3.10+incompatible github.com/gliderlabs/ssh v0.2.2 // indirect github.com/google/go-cmp v0.3.0 // indirect github.com/google/go-github/v25 v25.1.3 diff --git a/go.sum b/go.sum index 5a9a17d..8741862 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo= github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 3f73b95..f5bddc4 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -25,6 +25,7 @@ type Client struct { context context.Context release *github.RepositoryRelease baseURL string + log *log.Entry } // New initialize a new GitHubRelease @@ -52,6 +53,7 @@ func New(c *config.GitHubProvider) (*Client, error) { client: client, context: ctx, baseURL: baseURL, + log: log.WithField("releaser", GITHUB), }, err } @@ -85,7 +87,7 @@ func (g *Client) ValidateConfig() error { func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { tag := releaseVersion.Next.Version.String() - log.Debugf("create release with version %s", tag) + g.log.Debugf("create release with version %s", tag) prerelease := releaseVersion.Next.Version.Prerelease() != "" @@ -102,11 +104,11 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC 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) + g.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") + g.log.Debugf("Release repsone: %+v", *release) + g.log.Infof("Crated release") } return nil @@ -133,7 +135,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { } if resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf("releaser: github: Could not upload asset %s: %s", file.Name(), resp.Status) + return fmt.Errorf("could not upload asset %s: %s", file.Name(), resp.Status) } } } diff --git a/internal/releaser/gitlab/gitlab.go b/internal/releaser/gitlab/gitlab.go index b739232..173f18e 100644 --- a/internal/releaser/gitlab/gitlab.go +++ b/internal/releaser/gitlab/gitlab.go @@ -32,6 +32,7 @@ type Client struct { apiURL string token string release string + log *log.Entry } // New initialize a new gitlabRelease @@ -41,16 +42,6 @@ func New(config *config.GitLabProvider) (*Client, error) { 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") @@ -63,9 +54,10 @@ func New(config *config.GitLabProvider) (*Client, error) { token: accessToken, config: config, context: ctx, - baseURL: baseURL, - apiURL: baseURL + "api/v4", + baseURL: config.CustomURL, + apiURL: config.CustomURL + "api/v4", client: httpClient, + log: log.WithField("releaser", GITLAB), }, nil } @@ -81,12 +73,21 @@ func (g *Client) GetCompareURL(oldVersion, newVersion string) string { //ValidateConfig for gitlab func (g *Client) ValidateConfig() error { - log.Debugf("validate gitlab provider config") + g.log.Debugf("validate gitlab provider config") if g.config.Repo == "" { return fmt.Errorf("gitlab Repro is not set") } + g.config.Repo = strings.Trim(g.config.Repo, "/") + + if g.config.CustomURL == "" { + g.config.CustomURL = "https://gitlab.com" + } + + g.config.CustomURL = strings.Trim(g.config.CustomURL, "/") + g.log.Debugf("Use gitlab url %s", g.config.CustomURL) + return nil } @@ -96,7 +97,9 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC tag := releaseVersion.Next.Version.String() g.release = tag - log.Debugf("create release with version %s", tag) + g.log.Debugf("create release with version %s", tag) + url := fmt.Sprintf("%s/projects/%s/releases", g.apiURL, util.PathEscape(g.config.Repo)) + g.log.Debugf("Send release to %s", url) bodyBytes, err := json.Marshal(Release{ TagName: tag, @@ -107,36 +110,32 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC 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) + req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) + if err != nil { + return err + } - resp, err := g.client.Post(url, "application/json", bodyReader) + req.Header.Set("Content-Type", "application/json") + resp, err := util.Do(g.client, req, nil) 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 fmt.Errorf("could not create release: %s", err.Error()) } + respBodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + log.Debugf("Release repsone: %+v", string(respBodyBytes)) + + if err := util.IsValidResult(resp); err != nil { + return err + } + + log.Infof("Crated release") + return nil } @@ -149,15 +148,16 @@ 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) if err != nil { - return fmt.Errorf("releaser: gitlab: Could not upload asset %s: %s", file.Name(), err.Error()) + return fmt.Errorf("could not upload asset %s: %s", file.Name(), err.Error()) } downloadURL := fmt.Sprintf("%s%s%s", g.baseURL, g.config.Repo, result.URL) @@ -182,7 +182,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { return err } - log.Infof("Link file with release %s done", g.release) + log.Infof("Link file with release %s is done", g.release) } return nil } @@ -216,6 +216,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 b535df2..7956824 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -185,8 +185,5 @@ func IsValidResult(resp *http.Response) error { } func ShouldRetry(resp *http.Response) bool { - if resp.StatusCode == http.StatusTooManyRequests { - return true - } - return false + return resp.StatusCode == http.StatusTooManyRequests } From 467ae1f87e5a3319794d2a140d3771da5da21a48 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:31:30 +0200 Subject: [PATCH 6/8] build(releaser): fix release config --- .release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.release.yml b/.release.yml index 1d2b869..608032e 100644 --- a/.release.yml +++ b/.release.yml @@ -9,7 +9,7 @@ changelog: printAll: false template: '' templatePath: '' -release: 'gitlab' +release: 'github' assets: - name: go-semantic-release compress: false @@ -19,6 +19,3 @@ github: repo: "go-semantic-release" user: "nightapes" customUrl: "" -gitlab: - repo: "test/bla" - customUrl: "http://gitlab.example.com" From 829fea1282158991fb9d428bc1bcf613055a2fba Mon Sep 17 00:00:00 2001 From: Nightapes Date: Thu, 8 Aug 2019 21:31:30 +0200 Subject: [PATCH 7/8] build(releaser): fix release config --- .release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.release.yml b/.release.yml index 608032e..4f1a577 100644 --- a/.release.yml +++ b/.release.yml @@ -11,10 +11,10 @@ changelog: templatePath: '' release: 'github' assets: - - name: go-semantic-release + - name: ./build/go-semantic-release + compress: false + - name: ./build/go-semantic-release.exe compress: false - - name: go-semantic-release.exe - compress: true github: repo: "go-semantic-release" user: "nightapes" From 7b16b164f2fec62b34d93257017dd15518ec1d61 Mon Sep 17 00:00:00 2001 From: Nightapes Date: Sun, 11 Aug 2019 15:03:55 +0200 Subject: [PATCH 8/8] test(gitlab): add missing tests --- internal/releaser/github/github.go | 4 +- internal/releaser/gitlab/gitlab.go | 73 +++--- internal/releaser/gitlab/gitlab_test.go | 327 ++++++++++++++++++++++++ internal/releaser/releaser.go | 7 +- internal/releaser/util/util.go | 19 +- internal/releaser/util/util_test.go | 111 ++++++++ 6 files changed, 480 insertions(+), 61 deletions(-) create mode 100644 internal/releaser/gitlab/gitlab_test.go diff --git a/internal/releaser/github/github.go b/internal/releaser/github/github.go index 2ba7eab..0f5af06 100644 --- a/internal/releaser/github/github.go +++ b/internal/releaser/github/github.go @@ -91,6 +91,8 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC prerelease := releaseVersion.Next.Version.Prerelease() != "" + log.Debugf("Send %+v", generatedChangelog) + release, _, err := g.client.Repositories.CreateRelease(g.context, g.config.User, g.config.Repo, &github.RepositoryRelease{ TagName: &tag, TargetCommitish: &releaseVersion.Branch, @@ -104,7 +106,7 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC log.Infof("A release with tag %s already exits, will not perform a release or update", tag) return nil } - return fmt.Errorf("could not create release: %s", err.Error()) + return fmt.Errorf("could not create release: %s", err.Error()) } g.release = release log.Debugf("Release repsone: %+v", *release) diff --git a/internal/releaser/gitlab/gitlab.go b/internal/releaser/gitlab/gitlab.go index 173f18e..1f2b465 100644 --- a/internal/releaser/gitlab/gitlab.go +++ b/internal/releaser/gitlab/gitlab.go @@ -31,17 +31,12 @@ type Client struct { baseURL string apiURL string token string - release string + Release string log *log.Entry } // New initialize a new gitlabRelease -func New(config *config.GitLabProvider) (*Client, error) { - accessToken, err := util.GetAccessToken(GITLAB) - if err != nil { - return nil, err - } - +func New(config *config.GitLabProvider, accessToken string) (*Client, error) { ctx := context.Background() tokenHeader := util.NewAddHeaderTransport(nil, "PRIVATE-TOKEN", accessToken) acceptHeader := util.NewAddHeaderTransport(tokenHeader, "Accept", "application/json") @@ -50,44 +45,46 @@ func New(config *config.GitLabProvider) (*Client, error) { Timeout: time.Second * 60, } + logger := log.WithField("releaser", GITLAB) + + logger.Debugf("validate gitlab provider config") + + if config.Repo == "" { + return nil, fmt.Errorf("gitlab Repro is not set") + } + + config.Repo = strings.Trim(config.Repo, "/") + + if config.CustomURL == "" { + config.CustomURL = "https://gitlab.com" + } + + config.CustomURL = strings.Trim(config.CustomURL, "/") + logger.Debugf("Use gitlab url %s", config.CustomURL) + return &Client{ token: accessToken, config: config, context: ctx, baseURL: config.CustomURL, - apiURL: config.CustomURL + "api/v4", + apiURL: config.CustomURL + "/api/v4", client: httpClient, - log: log.WithField("releaser", GITLAB), + log: logger, }, nil } //GetCommitURL for gitlab func (g *Client) GetCommitURL() string { - return fmt.Sprintf("%s%s/commit/{{hash}}", g.baseURL, g.config.Repo) + 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) + return fmt.Sprintf("%s/%s/compare/%s...%s", g.baseURL, g.config.Repo, oldVersion, newVersion) } //ValidateConfig for gitlab func (g *Client) ValidateConfig() error { - g.log.Debugf("validate gitlab provider config") - - if g.config.Repo == "" { - return fmt.Errorf("gitlab Repro is not set") - } - - g.config.Repo = strings.Trim(g.config.Repo, "/") - - if g.config.CustomURL == "" { - g.config.CustomURL = "https://gitlab.com" - } - - g.config.CustomURL = strings.Trim(g.config.CustomURL, "/") - g.log.Debugf("Use gitlab url %s", g.config.CustomURL) - return nil } @@ -96,10 +93,10 @@ func (g *Client) ValidateConfig() error { func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedChangelog *shared.GeneratedChangelog) error { tag := releaseVersion.Next.Version.String() - g.release = tag - g.log.Debugf("create release with version %s", tag) + g.Release = tag + g.log.Infof("create release with version %s", tag) url := fmt.Sprintf("%s/projects/%s/releases", g.apiURL, util.PathEscape(g.config.Repo)) - g.log.Debugf("Send release to %s", url) + g.log.Infof("Send release to %s", url) bodyBytes, err := json.Marshal(Release{ TagName: tag, @@ -113,23 +110,15 @@ func (g *Client) CreateRelease(releaseVersion *shared.ReleaseVersion, generatedC req, err := http.NewRequest("POST", url, bytes.NewReader(bodyBytes)) if err != nil { - return err + return fmt.Errorf("could not create request: %s", err.Error()) } - req.Header.Set("Content-Type", "application/json") resp, err := util.Do(g.client, req, nil) if err != nil { - return fmt.Errorf("could not create release: %s", err.Error()) } - respBodyBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - log.Debugf("Release repsone: %+v", string(respBodyBytes)) - if err := util.IsValidResult(resp); err != nil { return err } @@ -160,18 +149,18 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { return fmt.Errorf("could not upload asset %s: %s", file.Name(), err.Error()) } - downloadURL := fmt.Sprintf("%s%s%s", g.baseURL, g.config.Repo, result.URL) + 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) + 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) + log.Infof("Link file %s with release %s", file.Name(), g.Release) resp, err := util.Do(g.client, req, nil) if err != nil { @@ -182,7 +171,7 @@ func (g *Client) UploadAssets(repoDir string, assets []config.Asset) error { return err } - log.Infof("Link file with release %s is done", g.release) + log.Infof("Link file with release %s is done", g.Release) } return nil } diff --git a/internal/releaser/gitlab/gitlab_test.go b/internal/releaser/gitlab/gitlab_test.go new file mode 100644 index 0000000..944dd87 --- /dev/null +++ b/internal/releaser/gitlab/gitlab_test.go @@ -0,0 +1,327 @@ +package gitlab_test + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/Masterminds/semver" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + + //"github.com/Nightapes/go-semantic-release/internal/releaser/util" + "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" +) + +func TestGetCommitURL(t *testing.T) { + + client, err := gitlab.New(&config.GitLabProvider{ + CustomURL: "https://localhost/", + Repo: "test/test", + }, "aToken") + assert.NoError(t, err) + assert.Equal(t, "https://localhost/test/test/commit/{{hash}}", client.GetCommitURL()) +} + +func TestGetCompareURL(t *testing.T) { + + client, err := gitlab.New(&config.GitLabProvider{ + CustomURL: "https://localhost/", + Repo: "test/test", + }, "aToken") + assert.NoError(t, err) + assert.Equal(t, "https://localhost/test/test/compare/1.0.0...1.0.1", client.GetCompareURL("1.0.0", "1.0.1")) +} + +func TestValidateConfig_EmptyRepro(t *testing.T) { + _, err := gitlab.New(&config.GitLabProvider{ + CustomURL: "https://localhost/", + }, "aToken") + assert.Error(t, err) +} + +func TestValidateConfig_DefaultURL(t *testing.T) { + config := &config.GitLabProvider{ + Repo: "localhost/test", + } + _, err := gitlab.New(config, "aToken") + assert.NoError(t, err) + assert.Equal(t, "https://gitlab.com", config.CustomURL) +} + +func TestValidateConfig_CustomURL(t *testing.T) { + config := &config.GitLabProvider{ + Repo: "/localhost/test/", + CustomURL: "https://localhost/", + } + _, err := gitlab.New(config, "aToken") + assert.NoError(t, err) + assert.Equal(t, "https://localhost", config.CustomURL) + assert.Equal(t, "localhost/test", config.Repo) +} + +func TestCreateRelease(t *testing.T) { + + lastVersion, _ := semver.NewVersion("1.0.0") + newVersion, _ := semver.NewVersion("2.0.0") + + testReleases := []struct { + config config.GitLabProvider + releaseVersion *shared.ReleaseVersion + generatedChangelog *shared.GeneratedChangelog + responseBody string + responseCode int + requestBody string + valid bool + }{ + { + config: config.GitLabProvider{ + Repo: "foo/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", + }, + responseBody: "{}", + responseCode: 200, + requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`, + valid: true, + }, + { + config: config.GitLabProvider{ + Repo: "foo/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", + }, + responseBody: "{}", + responseCode: 500, + requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`, + valid: false, + }, + { + config: config.GitLabProvider{ + Repo: "foo/bar", + CustomURL: "broken", + }, + 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", + }, + responseCode: 400, + responseBody: "{}", + requestBody: `{"tag_name":"2.0.0","name":"title","ref":"master","description":"content","assets":{"links":null}}`, + valid: false, + }, + } + + for _, testObject := range testReleases { + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + log.Infof("Got call from %s %s", req.Method, req.URL.String()) + + assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken") + assert.Equal(t, req.Header.Get("Accept"), "application/json") + + bodyBytes, err := ioutil.ReadAll(req.Body) + if err != nil { + log.Fatal(err) + } + assert.Equal(t, testObject.requestBody, string(bodyBytes)) + + rw.WriteHeader(testObject.responseCode) + rw.Header().Set("Content-Type", "application/json") + + if _, err := rw.Write([]byte(testObject.responseBody)); err != nil { + log.Info(err) + } + + })) + + if testObject.config.CustomURL == "" { + testObject.config.CustomURL = testServer.URL + } + + client, err := gitlab.New(&testObject.config, "aToken") + assert.NoError(t, err) + + err = client.CreateRelease(testObject.releaseVersion, testObject.generatedChangelog) + if err != nil { + t.Log(err) + } + assert.Equal(t, testObject.valid, err == nil) + + testServer.Close() + + } +} + +func TestUploadAssets(t *testing.T) { + + file, err := ioutil.TempFile("", "prefix") + if err != nil { + log.Fatal(err) + } + defer os.Remove(file.Name()) + + _, err = file.WriteString("testFile") + assert.NoError(t, err) + + testReleases := []struct { + config config.GitLabProvider + responseBody []string + responseCode []int + assets []config.Asset + requestBody []string + testDir string + url []string + method []string + valid bool + }{ + { + config: config.GitLabProvider{ + Repo: "foo/bar", + }, + responseBody: []string{`{"alt" : "dk", "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png", "markdown" :"![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"}`, ""}, + responseCode: []int{200, 200}, + requestBody: []string{ + filepath.Base(file.Name()), ""}, + url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=/foo/bar/uploads/"}, + method: []string{"POST", "POST"}, + valid: true, + testDir: os.TempDir(), + assets: []config.Asset{ + config.Asset{ + Name: filepath.Base(file.Name()), + Compress: false, + }, + }, + }, + { + config: config.GitLabProvider{ + Repo: "foo/bar", + }, + responseBody: []string{`{"alt" : "dk", "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png", "markdown" :"![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"}`, ""}, + responseCode: []int{400, 200}, + requestBody: []string{ + filepath.Base(file.Name()), ""}, + url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=/foo/bar/uploads/"}, + method: []string{"POST", "POST"}, + valid: false, + testDir: os.TempDir(), + assets: []config.Asset{ + config.Asset{ + Name: filepath.Base(file.Name()), + Compress: false, + }, + }, + }, + { + config: config.GitLabProvider{ + Repo: "foo/bar", + }, + responseBody: []string{`broken`, ""}, + responseCode: []int{200, 200}, + requestBody: []string{ + filepath.Base(file.Name()), ""}, + url: []string{`/api/v4/projects/foo%2Fbar/uploads`, "/api/v4/projects/foo%2Fbar/releases/1.0.0/assets/links?name=" + filepath.Base(file.Name()) + "&url=/foo/bar/uploads/"}, + method: []string{"POST", "POST"}, + valid: false, + testDir: os.TempDir(), + assets: []config.Asset{ + config.Asset{ + Name: filepath.Base(file.Name()), + Compress: false, + }, + }, + }, + } + + for _, testObject := range testReleases { + calls := 0 + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + log.Infof("Got call from %s %s", req.Method, req.URL.String()) + + assert.Contains(t, req.URL.String(), strings.ReplaceAll(testObject.url[calls], "", testObject.config.CustomURL)) + assert.Equal(t, req.Method, testObject.method[calls]) + + assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken") + assert.Equal(t, req.Header.Get("Accept"), "application/json") + + bodyBytes, err := ioutil.ReadAll(req.Body) + if err != nil { + log.Fatal(err) + } + assert.Contains(t, string(bodyBytes), testObject.requestBody[calls]) + + rw.WriteHeader(testObject.responseCode[calls]) + rw.Header().Set("Content-Type", "application/json") + + if _, err := rw.Write([]byte(testObject.responseBody[calls])); err != nil { + log.Info(err) + } + calls++ + })) + + if testObject.config.CustomURL == "" { + testObject.config.CustomURL = testServer.URL + } + + client, err := gitlab.New(&testObject.config, "aToken") + assert.NoError(t, err) + client.Release = "1.0.0" + + err = client.UploadAssets(testObject.testDir, testObject.assets) + if err != nil { + t.Log(err) + } + assert.Equal(t, testObject.valid, err == nil) + + testServer.Close() + + } +} diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go index b2a9863..d96f49f 100644 --- a/internal/releaser/releaser.go +++ b/internal/releaser/releaser.go @@ -5,6 +5,7 @@ import ( "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/releaser/util" "github.com/Nightapes/go-semantic-release/internal/shared" "github.com/Nightapes/go-semantic-release/pkg/config" @@ -40,7 +41,11 @@ func (r *Releasers) GetReleaser() (Releaser, error) { return github.New(&r.config.GitHubProvider) case gitlab.GITLAB: log.Debugf("initialize new %s-provider", gitlab.GITLAB) - return gitlab.New(&r.config.GitLabProvider) + accessToken, err := util.GetAccessToken(gitlab.GITLAB) + if err != nil { + return nil, err + } + return gitlab.New(&r.config.GitLabProvider, accessToken) } 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 61523fc..5bde7e3 100644 --- a/internal/releaser/util/util.go +++ b/internal/releaser/util/util.go @@ -133,21 +133,6 @@ 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) @@ -162,7 +147,7 @@ func Do(client *http.Client, req *http.Request, v interface{}) (*http.Response, defer resp.Body.Close() switch resp.StatusCode { - case 200, 201, 202, 204, 304: + case 200, 201, 202, 204: if v != nil { if w, ok := v.(io.Writer); ok { _, err = io.Copy(w, resp.Body) @@ -178,7 +163,7 @@ func Do(client *http.Client, req *http.Request, v interface{}) (*http.Response, // IsValidResult validates response code func IsValidResult(resp *http.Response) error { switch resp.StatusCode { - case 200, 201, 202, 204, 304: + case 200, 201, 202, 204: return nil default: return fmt.Errorf("%s %s: %d", resp.Request.Method, resp.Request.URL, resp.StatusCode) diff --git a/internal/releaser/util/util_test.go b/internal/releaser/util/util_test.go index 9cf12b4..52822ea 100644 --- a/internal/releaser/util/util_test.go +++ b/internal/releaser/util/util_test.go @@ -3,9 +3,15 @@ package util_test import ( "context" "fmt" + "net/http" + "net/http/httptest" + "net/url" "os" "strings" "testing" + "time" + + log "github.com/sirupsen/logrus" "github.com/Nightapes/go-semantic-release/pkg/config" @@ -117,3 +123,108 @@ func TestPrepareAssets(t *testing.T) { } } + +func TestShouldRetry(t *testing.T) { + assert.True(t, util.ShouldRetry(&http.Response{StatusCode: 429})) + assert.False(t, util.ShouldRetry(&http.Response{StatusCode: 200})) +} + +func TestIsValidResult(t *testing.T) { + assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 200})) + assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 201})) + assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 202})) + assert.NoError(t, util.IsValidResult(&http.Response{StatusCode: 204})) + + u, err := url.Parse("https://localhost") + assert.NoError(t, err) + assert.Error(t, util.IsValidResult(&http.Response{StatusCode: 500, Request: &http.Request{ + Method: "POST", + URL: u, + }})) +} + +func TestPathEscape(t *testing.T) { + assert.Equal(t, "test%2Ftest", util.PathEscape("test/test")) + assert.Equal(t, "test", util.PathEscape("test")) + assert.Equal(t, "test%2Etest", util.PathEscape("test.test")) +} + +type example struct { + Test string `json:"test"` +} + +func TestDoAndRoundTrip(t *testing.T) { + tokenHeader := util.NewAddHeaderTransport(nil, "PRIVATE-TOKEN", "aToken") + acceptHeader := util.NewAddHeaderTransport(tokenHeader, "Accept", "application/json") + httpClient := &http.Client{ + Transport: acceptHeader, + Timeout: time.Second * 60, + } + + testsDoMethod := []struct { + statusCode int + body string + responseBody interface{} + responseBodyType interface{} + hasError bool + path string + }{ + { + statusCode: 200, + body: `{"test" : "hallo"}`, + responseBody: &example{ + Test: "hallo", + }, + responseBodyType: &example{}, + hasError: false, + path: "", + }, + { + statusCode: 400, + body: `{"test" : "hallo"}`, + responseBody: &example{}, + responseBodyType: &example{}, + hasError: false, + path: "", + }, + { + statusCode: 200, + body: `{"test" : "hallo"}`, + hasError: true, + responseBody: &example{}, + responseBodyType: &example{}, + path: "broken", + }, + } + + for _, testOject := range testsDoMethod { + testServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + + log.Infof("Got call from %s %s", req.Method, req.URL.String()) + + assert.Equal(t, req.Header.Get("PRIVATE-TOKEN"), "aToken") + assert.Equal(t, req.Header.Get("Accept"), "application/json") + + rw.WriteHeader(testOject.statusCode) + rw.Header().Set("Content-Type", "application/json") + + if _, err := rw.Write([]byte(testOject.body)); err != nil { + log.Info(err) + } + + })) + + req, err := http.NewRequest("POST", testServer.URL+testOject.path, nil) + assert.NoError(t, err) + + resp, err := util.Do(httpClient, req, testOject.responseBodyType) + + assert.Equal(t, testOject.hasError, err != nil) + + if !testOject.hasError { + assert.Equal(t, testOject.statusCode, resp.StatusCode) + assert.Equal(t, testOject.responseBody, testOject.responseBodyType) + } + testServer.Close() + } +}