Skip to content
This repository was archived by the owner on Jul 12, 2022. It is now read-only.

Commit

Permalink
feat: add bitbucket as a provider to add repos (#823)
Browse files Browse the repository at this point in the history
* feat: add bitbucket as a provider to add repos

Signed-off-by: lucas.dittrich <lucas.dittrich@zup.com.br>
  • Loading branch information
lucasdittrichzup authored Jan 22, 2021
1 parent e271fdb commit e733679
Show file tree
Hide file tree
Showing 6 changed files with 858 additions and 0 deletions.
48 changes: 48 additions & 0 deletions internal/mocks/bitbucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package mocks

import "github.com/stretchr/testify/mock"

type RepoInfo struct {
mock.Mock
}

func (ri *RepoInfo) ZipUrl(version string) string {
args := ri.Called(version)
return args.String(0)
}

func (ri *RepoInfo) TagsUrl() string {
args := ri.Called()
return args.String(0)
}

func (ri *RepoInfo) LatestTagUrl() string {
args := ri.Called()
return args.String(0)
}

func (ri *RepoInfo) TokenHeader() string {
args := ri.Called()
return args.String(0)
}

func (ri *RepoInfo) Token() string {
args := ri.Called()
return args.String(0)
}
3 changes: 3 additions & 0 deletions pkg/commands/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/ZupIT/ritchie-cli/pkg/formula/runner/local"
"github.com/ZupIT/ritchie-cli/pkg/formula/tree"
fworkspace "github.com/ZupIT/ritchie-cli/pkg/formula/workspace"
"github.com/ZupIT/ritchie-cli/pkg/git/bitbucket"
"github.com/ZupIT/ritchie-cli/pkg/git/github"
"github.com/ZupIT/ritchie-cli/pkg/git/gitlab"
"github.com/ZupIT/ritchie-cli/pkg/metric"
Expand Down Expand Up @@ -89,10 +90,12 @@ func Build() *cobra.Command {

githubRepo := github.NewRepoManager(http.DefaultClient)
gitlabRepo := gitlab.NewRepoManager(http.DefaultClient)
bitbucketRepo := bitbucket.NewRepoManager(http.DefaultClient)

repoProviders := formula.NewRepoProviders()
repoProviders.Add("Github", formula.Git{Repos: githubRepo, NewRepoInfo: github.NewRepoInfo})
repoProviders.Add("Gitlab", formula.Git{Repos: gitlabRepo, NewRepoInfo: gitlab.NewRepoInfo})
repoProviders.Add("Bitbucket", formula.Git{Repos: bitbucketRepo, NewRepoInfo: bitbucket.NewRepoInfo})

treeGen := tree.NewGenerator(dirManager, fileManager)

Expand Down
90 changes: 90 additions & 0 deletions pkg/git/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitbucket

import (
"fmt"
"strings"

"github.com/ZupIT/ritchie-cli/pkg/git"
)

const (
ZipUrlPattern = "https://bitbucket.org/%s/%s/get/%s.zip"
TagsUrlPattern = "https://api.bitbucket.org/2.0/repositories/%s/%s/refs/tags"
LatestTagUrlPattern = "https://api.bitbucket.org/2.0/repositories/%s/%s/refs/tags?sort=target.date"
)

type DefaultRepoInfo struct {
host string
owner string
repo string
token string
}

const (
repoOffset = 4
ownerOffset = 5
hostOffset = 6
defaultUrl = 8
)

// NewRepoInfo returns the RepoInfo built by repository url
// Repository url e.g. https://bitbucket.org/{{owner}}/{{repo}}/src/master/
func NewRepoInfo(url string, token string) git.RepoInfo {
split := strings.Split(url, "/")
if len(split) < defaultUrl {
return DefaultRepoInfo{}
}

repo := split[len(split)-repoOffset]
owner := split[len(split)-ownerOffset]
host := split[len(split)-hostOffset]

return DefaultRepoInfo{
host: host,
owner: owner,
repo: repo,
token: token,
}
}

// ZipUrl returns the Bitbucket API URL for download zipball repository
// e.g. https://bitbucket.org/{{owner}}/{{repo}}/get/{{tag-version}}.zip
func (in DefaultRepoInfo) ZipUrl(version string) string {
return fmt.Sprintf(ZipUrlPattern, in.owner, in.repo, version)
}

// TagsUrl returns the Bitbucket API URL for get all tags
// e.g. https://api.bitbucket.org/2.0/repositories/{{owner}}/{{repo}}/refs/tags
func (in DefaultRepoInfo) TagsUrl() string {
return fmt.Sprintf(TagsUrlPattern, in.owner, in.repo)
}

// LatestTagUrl returns the Bitbucket API URL for get latest tag release
// e.g. https://api.bitbucket.org/2.0/repositories/{{owner}}/{{repo}}/refs/tags?sort=target.date
func (in DefaultRepoInfo) LatestTagUrl() string {
return fmt.Sprintf(LatestTagUrlPattern, in.owner, in.repo)
}

// TokenHeader returns the Authorization value formatted for Bitbucket API integration
func (in DefaultRepoInfo) TokenHeader() string {
return in.token
}

func (in DefaultRepoInfo) Token() string {
return in.token
}
102 changes: 102 additions & 0 deletions pkg/git/bitbucket/bitbucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitbucket

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewRepoInfo(t *testing.T) {
type in struct {
url string
token string
}
tests := []struct {
name string
in in
want DefaultRepoInfo
}{
{
name: "Run with success",
in: in{
url: "https://bitbucket.org/username/ritchie-formulas/src/master/",
token: "some_token",
},
want: DefaultRepoInfo{
host: "bitbucket.org",
owner: "username",
repo: "ritchie-formulas",
token: "some_token",
},
},
{
name: "Return err when the URL is incorrect",
in: in{
url: "",
token: "some_token",
},
want: DefaultRepoInfo{},
},
}

for _, tt := range tests {
got := NewRepoInfo(tt.in.url, tt.in.token)

assert.Equal(t, tt.want, got)
}
}

func TestTagsUrl(t *testing.T) {
const want = "https://api.bitbucket.org/2.0/repositories/username/ritchie-formulas/refs/tags"
repoInfo := NewRepoInfo("https://bitbucket.org/username/ritchie-formulas/src/master/", "some_token")
tagsUrl := repoInfo.TagsUrl()

assert.Equal(t, want, tagsUrl)
}

func TestZipUrl(t *testing.T) {
const want = "https://bitbucket.org/username/ritchie-formulas/get/1.0.0.zip"
repoInfo := NewRepoInfo("https://bitbucket.org/username/ritchie-formulas/src/master/", "some_token")
zipUrl := repoInfo.ZipUrl("1.0.0")

assert.Equal(t, want, zipUrl)
}

func TestLatestTagUrl(t *testing.T) {
const want = "https://api.bitbucket.org/2.0/repositories/username/ritchie-formulas/refs/tags?sort=target.date"
repoInfo := NewRepoInfo("https://bitbucket.org/username/ritchie-formulas/src/master/", "some_token")
latestTagsUrl := repoInfo.LatestTagUrl()

assert.Equal(t, want, latestTagsUrl)
}

func TestTokenHeader(t *testing.T) {
const want = "some_token"
repoInfo := NewRepoInfo("https://bitbucket.org/username/ritchie-formulas/src/master/", "some_token")
tokenHeader := repoInfo.TokenHeader()

assert.Equal(t, want, tokenHeader)
}

func TestToken(t *testing.T) {
const want = "some_token"
repoInfo := NewRepoInfo("https://bitbucket.org/username/ritchie-formulas/src/master/", "some_token")
token := repoInfo.Token()

assert.Equal(t, want, token)
}
135 changes: 135 additions & 0 deletions pkg/git/bitbucket/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitbucket

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"

"github.com/ZupIT/ritchie-cli/pkg/git"
"github.com/ZupIT/ritchie-cli/pkg/http/headers"
)

type RepoManager struct {
client *http.Client
}

type value struct {
Name string `json:"name"`
}

type bitbucketTags struct {
Values []value `json:"values"`
}

func NewRepoManager(client *http.Client) RepoManager {
return RepoManager{client: client}
}

func (re RepoManager) Zipball(info git.RepoInfo, version string) (io.ReadCloser, error) {
zipUrl := info.ZipUrl(version)
res, err := re.performRequest(info, zipUrl)
if err != nil {
return nil, err
}

if res.StatusCode != http.StatusOK {
defer res.Body.Close()
all, _ := ioutil.ReadAll(res.Body)
return nil, errors.New(res.Status + "-" + string(all))
}

return res.Body, nil
}

func (re RepoManager) Tags(info git.RepoInfo) (git.Tags, error) {
apiUrl := info.TagsUrl()
res, err := re.performRequest(info, apiUrl)
if err != nil {
return git.Tags{}, err
}

defer res.Body.Close()

if res.StatusCode != http.StatusOK {
all, _ := ioutil.ReadAll(res.Body)
return git.Tags{}, errors.New(res.Status + "-" + string(all))
}

var bTags bitbucketTags
if err := json.NewDecoder(res.Body).Decode(&bTags); err != nil {
return git.Tags{}, err
}

var tags git.Tags
for _, v := range bTags.Values {
tag := git.Tag{Name: v.Name}
tags = append(tags, tag)
}

return tags, nil
}

func (re RepoManager) LatestTag(info git.RepoInfo) (git.Tag, error) {
apiUrl := info.LatestTagUrl()
res, err := re.performRequest(info, apiUrl)
if err != nil {
return git.Tag{}, err
}

defer res.Body.Close()

if res.StatusCode != http.StatusOK {
all, _ := ioutil.ReadAll(res.Body)
return git.Tag{}, errors.New(res.Status + "-" + string(all))
}

var bTags bitbucketTags
if err := json.NewDecoder(res.Body).Decode(&bTags); err != nil {
return git.Tag{}, err
}

if len(bTags.Values) == 0 {
return git.Tag{}, errors.New("release not found")
}

latestTag := git.Tag{Name: bTags.Values[len(bTags.Values)-1].Name}

return latestTag, nil
}

func (re RepoManager) performRequest(info git.RepoInfo, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
return nil, err
}

if info.Token() != "" {
req.Header.Add(headers.Authorization, fmt.Sprintf("Bearer %s", info.Token()))
}

res, err := re.client.Do(req)
if err != nil {
return nil, err
}

return res, nil
}
Loading

0 comments on commit e733679

Please sign in to comment.