diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 638bb17ec..99d59a98e 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -4,12 +4,9 @@ package bitbucketserver // quality or security standards expected of this project. Please use with caution. import ( - "crypto/md5" "crypto/rsa" "crypto/tls" "crypto/x509" - "encoding/hex" - "encoding/json" "encoding/pem" "fmt" "github.com/drone/drone/model" @@ -98,7 +95,12 @@ func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, client := internal.NewClientWithToken(c.URL, c.Consumer, accessToken.Token) - return client.FindCurrentUser() + user, err := client.FindCurrentUser() + if err != nil { + return nil, err + } + + return convertUser(user, accessToken), nil } @@ -114,15 +116,24 @@ func (*Config) Teams(u *model.User) ([]*model.Team, error) { } func (c *Config) Repo(u *model.User, owner, name string) (*model.Repo, error) { - client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) - - return client.FindRepo(owner, name) + repo, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepo(owner, name) + if err != nil { + return nil, err + } + return convertRepo(repo), nil } func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { - client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) + repos, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepos() + if err != nil { + return nil, err + } + var all []*model.RepoLite + for _, repo := range repos { + all = append(all, convertRepoLite(repo)) + } - return client.FindRepos() + return all, nil } func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) { @@ -173,29 +184,7 @@ func (c *Config) Deactivate(u *model.User, r *model.Repo, link string) error { } func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { - hook := new(postHook) - if err := json.NewDecoder(r.Body).Decode(hook); err != nil { - return nil, nil, err - } - - build := &model.Build{ - Event: model.EventPush, - Ref: hook.RefChanges[0].RefID, // TODO check for index Values - Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values - Commit: hook.RefChanges[0].ToHash, // TODO check for index value - Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress), - Branch: strings.Split(hook.RefChanges[0].RefID, "refs/heads/")[1], - } - - repo := &model.Repo{ - Name: hook.Repository.Slug, - Owner: hook.Repository.Project.Key, - FullName: fmt.Sprintf("%s/%s", hook.Repository.Project.Key, hook.Repository.Slug), - Branch: "master", - Kind: model.RepoGit, - } - - return repo, build, nil + return parseHook(r) } func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer { @@ -215,11 +204,3 @@ func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) } return consumer } - -func avatarLink(email string) (url string) { - hasher := md5.New() - hasher.Write([]byte(strings.ToLower(email))) - emailHash := fmt.Sprintf("%v", hex.EncodeToString(hasher.Sum(nil))) - avatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s.jpg", emailHash) - return avatarURL -} diff --git a/remote/bitbucketserver/convert.go b/remote/bitbucketserver/convert.go new file mode 100644 index 000000000..6057a6270 --- /dev/null +++ b/remote/bitbucketserver/convert.go @@ -0,0 +1,97 @@ +package bitbucketserver + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/drone/drone/model" + "github.com/drone/drone/remote/bitbucketserver/internal" + "net/url" + "strings" + "github.com/mrjones/oauth" +) + +// convertRepo is a helper function used to convert a Bitbucket server repository +// structure to the common Drone repository structure. +func convertRepo(from *internal.Repo) *model.Repo { + + repo := model.Repo{ + Name: from.Slug, + Owner: from.Project.Key, + Branch: "master", + Kind: model.RepoGit, + IsPrivate: true, // Since we have to use Netrc it has to always be private :/ + FullName: fmt.Sprintf("%s/%s", from.Project.Key, from.Slug), + } + + for _, item := range from.Links.Clone { + if item.Name == "http" { + uri, err := url.Parse(item.Href) + if err != nil { + return nil + } + uri.User = nil + repo.Clone = uri.String() + } + } + for _, item := range from.Links.Self { + if item.Href != "" { + repo.Link = item.Href + } + } + log.Debug(fmt.Printf("Repo: %+v\n", repo)) + return &repo + +} + +// convertRepoLite is a helper function used to convert a Bitbucket repository +// structure to the simplified Drone repository structure. +func convertRepoLite(from *internal.Repo) *model.RepoLite { + return &model.RepoLite{ + Owner: from.Project.Key, + Name: from.Slug, + FullName: from.Project.Key + "/" + from.Slug, + //TODO: find the avatar for the repo + //Avatar: might need another ws call? + } + +} + +// convertPushHook is a helper function used to convert a Bitbucket push +// hook to the Drone build struct holding commit information. +func convertPushHook(hook *internal.PostHook) *model.Build { + build := &model.Build{ + Commit: hook.RefChanges[0].ToHash, // TODO check for index value + //Link: TODO find link + Branch: strings.Split(hook.RefChanges[0].RefID, "refs/heads/")[1], //TODO figure the correct for tags + //Message: TODO find the message for the commit + Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress), + Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values + //Timestamp: TODO find time parsing + Event: model.EventPush, //TODO: do more then PUSH find Tags etc + Ref: hook.RefChanges[0].RefID, // TODO check for index Values + + } + return build +} + +// convertUser is a helper function used to convert a Bitbucket user account +// structure to the Drone User structure. +func convertUser(from *internal.User, token *oauth.AccessToken) *model.User { + return &model.User{ + Login: from.Slug, + Token: token.Token, + Email: from.EmailAddress, + Avatar: avatarLink(from.EmailAddress), + } +} + +func avatarLink(email string) string { + hasher := md5.New() + hasher.Write([]byte(strings.ToLower(email))) + emailHash := fmt.Sprintf("%v", hex.EncodeToString(hasher.Sum(nil))) + avatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s.jpg", emailHash) + log.Debug(avatarURL) + return avatarURL +} diff --git a/remote/bitbucketserver/internal/client.go b/remote/bitbucketserver/internal/client.go index 657bd34ed..921b24206 100644 --- a/remote/bitbucketserver/internal/client.go +++ b/remote/bitbucketserver/internal/client.go @@ -11,7 +11,6 @@ import ( "github.com/mrjones/oauth" "io/ioutil" "net/http" - "net/url" "strings" ) @@ -32,10 +31,10 @@ type Client struct { accessToken string } -func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string) *Client { +func NewClientWithToken(url string, consumer *oauth.Consumer, AccessToken string) *Client { var token oauth.AccessToken token.Token = AccessToken - client, err := Consumer.MakeHttpClient(&token) + client, err := consumer.MakeHttpClient(&token) log.Debug(fmt.Printf("Create client: %+v %s\n", token, url)) if err != nil { log.Error(err) @@ -43,7 +42,7 @@ func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string return &Client{client, url, AccessToken} } -func (c *Client) FindCurrentUser() (*model.User, error) { +func (c *Client) FindCurrentUser() (*User, error) { CurrentUserIdResponse, err := c.client.Get(fmt.Sprintf(currentUserId, c.base)) if err != nil { return nil, err @@ -72,18 +71,11 @@ func (c *Client) FindCurrentUser() (*model.User, error) { return nil, err } - ModelUser := &model.User{ - Login: login, - Email: user.EmailAddress, - Token: c.accessToken, - Avatar: avatarLink(user.EmailAddress), - } - log.Debug(fmt.Printf("User information: %+v\n", ModelUser)) - return ModelUser, nil + return &user, nil } -func (c *Client) FindRepo(owner string, name string) (*model.Repo, error) { +func (c *Client) FindRepo(owner string, name string) (*Repo, error) { urlString := fmt.Sprintf(pathRepo, c.base, owner, name) response, err := c.client.Get(urlString) if err != nil { @@ -91,40 +83,15 @@ func (c *Client) FindRepo(owner string, name string) (*model.Repo, error) { } defer response.Body.Close() contents, err := ioutil.ReadAll(response.Body) - bsRepo := BSRepo{} - err = json.Unmarshal(contents, &bsRepo) + repo := Repo{} + err = json.Unmarshal(contents, &repo) if err != nil { return nil, err } - repo := &model.Repo{ - Name: bsRepo.Slug, - Owner: bsRepo.Project.Key, - Branch: "master", - Kind: model.RepoGit, - IsPrivate: true, // Since we have to use Netrc it has to always be private :/ - FullName: fmt.Sprintf("%s/%s", bsRepo.Project.Key, bsRepo.Slug), - } - - for _, item := range bsRepo.Links.Clone { - if item.Name == "http" { - uri, err := url.Parse(item.Href) - if err != nil { - return nil, err - } - uri.User = nil - repo.Clone = uri.String() - } - } - for _, item := range bsRepo.Links.Self { - if item.Href != "" { - repo.Link = item.Href - } - } - log.Debug(fmt.Printf("Repo: %+v\n", repo)) - return repo, nil + return &repo, nil } -func (c *Client) FindRepos() ([]*model.RepoLite, error) { +func (c *Client) FindRepos() ([]*Repo, error) { requestUrl := fmt.Sprintf(pathRepos, c.base, "1000") log.Debug(fmt.Printf("request :%s", requestUrl)) response, err := c.client.Get(requestUrl) @@ -142,16 +109,8 @@ func (c *Client) FindRepos() ([]*model.RepoLite, error) { return nil, err } log.Debug(fmt.Printf("repoResponse: %+v\n", repoResponse)) - var repos = []*model.RepoLite{} - for _, repo := range repoResponse.Values { - repos = append(repos, &model.RepoLite{ - Name: repo.Slug, - FullName: repo.Project.Key + "/" + repo.Slug, - Owner: repo.Project.Key, - }) - } - log.Debug(fmt.Printf("repos: %+v\n", repos)) - return repos, nil + + return repoResponse.Values, nil } func (c *Client) FindRepoPerms(owner string, repo string) (*model.Perm, error) { diff --git a/remote/bitbucketserver/internal/types.go b/remote/bitbucketserver/internal/types.go index ac222043b..8e4622ce2 100644 --- a/remote/bitbucketserver/internal/types.go +++ b/remote/bitbucketserver/internal/types.go @@ -15,7 +15,7 @@ type User struct { Type string `json:"type"` } -type BSRepo struct { +type Repo struct { Forkable bool `json:"forkable"` ID int `json:"id"` Links struct { @@ -49,42 +49,11 @@ type BSRepo struct { } type Repos struct { - IsLastPage bool `json:"isLastPage"` - Limit int `json:"limit"` - Size int `json:"size"` - Start int `json:"start"` - Values []struct { - Forkable bool `json:"forkable"` - ID int `json:"id"` - Links struct { - Clone []struct { - Href string `json:"href"` - Name string `json:"name"` - } `json:"clone"` - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` - Name string `json:"name"` - Project struct { - Description string `json:"description"` - ID int `json:"id"` - Key string `json:"key"` - Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` - Name string `json:"name"` - Public bool `json:"public"` - Type string `json:"type"` - } `json:"project"` - Public bool `json:"public"` - ScmID string `json:"scmId"` - Slug string `json:"slug"` - State string `json:"state"` - StatusMessage string `json:"statusMessage"` - } `json:"values"` + IsLastPage bool `json:"isLastPage"` + Limit int `json:"limit"` + Size int `json:"size"` + Start int `json:"start"` + Values []*Repo `json:"values"` } type Hook struct { @@ -100,3 +69,87 @@ type HookDetail struct { Version string `json:"version"` ConfigFormKey string `json:"configFormKey"` } + +type PostHook struct { + Changesets struct { + Filter interface{} `json:"filter"` + IsLastPage bool `json:"isLastPage"` + Limit int `json:"limit"` + Size int `json:"size"` + Start int `json:"start"` + Values []struct { + Changes struct { + Filter interface{} `json:"filter"` + IsLastPage bool `json:"isLastPage"` + Limit int `json:"limit"` + Size int `json:"size"` + Start int `json:"start"` + Values []struct { + ContentID string `json:"contentId"` + Executable bool `json:"executable"` + Link struct { + Rel string `json:"rel"` + URL string `json:"url"` + } `json:"link"` + NodeType string `json:"nodeType"` + Path struct { + Components []string `json:"components"` + Extension string `json:"extension"` + Name string `json:"name"` + Parent string `json:"parent"` + ToString string `json:"toString"` + } `json:"path"` + PercentUnchanged int `json:"percentUnchanged"` + SrcExecutable bool `json:"srcExecutable"` + Type string `json:"type"` + } `json:"values"` + } `json:"changes"` + FromCommit struct { + DisplayID string `json:"displayId"` + ID string `json:"id"` + } `json:"fromCommit"` + Link struct { + Rel string `json:"rel"` + URL string `json:"url"` + } `json:"link"` + ToCommit struct { + Author struct { + EmailAddress string `json:"emailAddress"` + Name string `json:"name"` + } `json:"author"` + AuthorTimestamp int `json:"authorTimestamp"` + DisplayID string `json:"displayId"` + ID string `json:"id"` + Message string `json:"message"` + Parents []struct { + DisplayID string `json:"displayId"` + ID string `json:"id"` + } `json:"parents"` + } `json:"toCommit"` + } `json:"values"` + } `json:"changesets"` + RefChanges []struct { + FromHash string `json:"fromHash"` + RefID string `json:"refId"` + ToHash string `json:"toHash"` + Type string `json:"type"` + } `json:"refChanges"` + Repository struct { + Forkable bool `json:"forkable"` + ID int `json:"id"` + Name string `json:"name"` + Project struct { + ID int `json:"id"` + IsPersonal bool `json:"isPersonal"` + Key string `json:"key"` + Name string `json:"name"` + Public bool `json:"public"` + Type string `json:"type"` + } `json:"project"` + Public bool `json:"public"` + ScmID string `json:"scmId"` + Slug string `json:"slug"` + State string `json:"state"` + StatusMessage string `json:"statusMessage"` + } `json:"repository"` +} diff --git a/remote/bitbucketserver/parse.go b/remote/bitbucketserver/parse.go new file mode 100644 index 000000000..48e0491f0 --- /dev/null +++ b/remote/bitbucketserver/parse.go @@ -0,0 +1,28 @@ +package bitbucketserver + +import ( + "encoding/json" + "fmt" + "github.com/drone/drone/model" + "github.com/drone/drone/remote/bitbucketserver/internal" + "net/http" +) + +// parseHook parses a Bitbucket hook from an http.Request request and returns +// Repo and Build detail. TODO: find a way to support PR hooks +func parseHook(r *http.Request) (*model.Repo, *model.Build, error) { + hook := new(internal.PostHook) + if err := json.NewDecoder(r.Body).Decode(hook); err != nil { + return nil, nil, err + } + build := convertPushHook(hook) + repo := &model.Repo{ + Name: hook.Repository.Slug, + Owner: hook.Repository.Project.Key, + FullName: fmt.Sprintf("%s/%s", hook.Repository.Project.Key, hook.Repository.Slug), + Branch: "master", + Kind: model.RepoGit, + } + + return repo, build, nil +} diff --git a/remote/bitbucketserver/types.go b/remote/bitbucketserver/types.go deleted file mode 100644 index 9b77600e9..000000000 --- a/remote/bitbucketserver/types.go +++ /dev/null @@ -1,87 +0,0 @@ -package bitbucketserver - -type postHook struct { - Changesets struct { - Filter interface{} `json:"filter"` - IsLastPage bool `json:"isLastPage"` - Limit int `json:"limit"` - Size int `json:"size"` - Start int `json:"start"` - Values []struct { - Changes struct { - Filter interface{} `json:"filter"` - IsLastPage bool `json:"isLastPage"` - Limit int `json:"limit"` - Size int `json:"size"` - Start int `json:"start"` - Values []struct { - ContentID string `json:"contentId"` - Executable bool `json:"executable"` - Link struct { - Rel string `json:"rel"` - URL string `json:"url"` - } `json:"link"` - NodeType string `json:"nodeType"` - Path struct { - Components []string `json:"components"` - Extension string `json:"extension"` - Name string `json:"name"` - Parent string `json:"parent"` - ToString string `json:"toString"` - } `json:"path"` - PercentUnchanged int `json:"percentUnchanged"` - SrcExecutable bool `json:"srcExecutable"` - Type string `json:"type"` - } `json:"values"` - } `json:"changes"` - FromCommit struct { - DisplayID string `json:"displayId"` - ID string `json:"id"` - } `json:"fromCommit"` - Link struct { - Rel string `json:"rel"` - URL string `json:"url"` - } `json:"link"` - ToCommit struct { - Author struct { - EmailAddress string `json:"emailAddress"` - Name string `json:"name"` - } `json:"author"` - AuthorTimestamp int `json:"authorTimestamp"` - DisplayID string `json:"displayId"` - ID string `json:"id"` - Message string `json:"message"` - Parents []struct { - DisplayID string `json:"displayId"` - ID string `json:"id"` - } `json:"parents"` - } `json:"toCommit"` - } `json:"values"` - } `json:"changesets"` - RefChanges []struct { - FromHash string `json:"fromHash"` - RefID string `json:"refId"` - ToHash string `json:"toHash"` - Type string `json:"type"` - } `json:"refChanges"` - Repository struct { - Forkable bool `json:"forkable"` - ID int `json:"id"` - Name string `json:"name"` - Project struct { - ID int `json:"id"` - IsPersonal bool `json:"isPersonal"` - Key string `json:"key"` - Name string `json:"name"` - Public bool `json:"public"` - Type string `json:"type"` - } `json:"project"` - Public bool `json:"public"` - ScmID string `json:"scmId"` - Slug string `json:"slug"` - State string `json:"state"` - StatusMessage string `json:"statusMessage"` - } `json:"repository"` -} - -