From 6624ff0ce6054986178628a3f621cb5419b88bdb Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sat, 25 Jun 2016 16:45:33 -0700 Subject: [PATCH 1/9] Start of refactor to stash implementation to match other remotes --- remote/bitbucketserver/bitbucketserver.go | 331 ++++++---------------- remote/bitbucketserver/client.go | 52 ---- remote/bitbucketserver/helper.go | 64 ----- remote/bitbucketserver/internal/client.go | 228 +++++++++++++++ remote/bitbucketserver/internal/types.go | 104 +++++++ remote/bitbucketserver/types.go | 54 +--- 6 files changed, 425 insertions(+), 408 deletions(-) delete mode 100644 remote/bitbucketserver/client.go delete mode 100644 remote/bitbucketserver/helper.go create mode 100644 remote/bitbucketserver/internal/client.go create mode 100644 remote/bitbucketserver/internal/types.go diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 196d44dd1..93dd50dc1 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -12,14 +12,24 @@ import ( "io/ioutil" "net/http" "net/url" - - log "github.com/Sirupsen/logrus" + "github.com/drone/drone/remote/bitbucketserver/internal" "github.com/drone/drone/model" "github.com/drone/drone/remote" "github.com/mrjones/oauth" "strings" + "crypto/tls" + "encoding/hex" + "crypto/md5" ) +const ( + requestTokenURL = "%s/plugins/servlet/oauth/request-token" + authorizeTokenURL = "%s/plugins/servlet/oauth/authorize" + accessTokenURL = "%s/plugins/servlet/oauth/access-token" +) + + + // Opts defines configuration options. type Opts struct { URL string // Stash server url. @@ -27,218 +37,121 @@ type Opts struct { Password string // Git machine account password. ConsumerKey string // Oauth1 consumer key. ConsumerRSA string // Oauth1 consumer key file. - SkipVerify bool // Skip ssl verification. } +type Config struct { + URL string + Username string + Password string + PrivateKey *rsa.PrivateKey + ConsumerKey string + SkipVerify bool + +} + // New returns a Remote implementation that integrates with Bitbucket Server, // the on-premise edition of Bitbucket Cloud, formerly known as Stash. func New(opts Opts) (remote.Remote, error) { - bb := &client{ - URL: opts.URL, + config := &Config{ + URL: opts.URL, + Username: opts.Username, + Password: opts.Password, ConsumerKey: opts.ConsumerKey, - ConsumerRSA: opts.ConsumerRSA, - GitUserName: opts.Username, - GitPassword: opts.Password, + SkipVerify: opts.SkipVerify, } switch { - case bb.GitUserName == "": + case opts.Username == "": return nil, fmt.Errorf("Must have a git machine account username") - case bb.GitPassword == "": + case opts.Password == "": return nil, fmt.Errorf("Must have a git machine account password") - case bb.ConsumerKey == "": + case opts.ConsumerKey == "": return nil, fmt.Errorf("Must have a oauth1 consumer key") - case bb.ConsumerRSA == "": + case opts.ConsumerRSA == "": return nil, fmt.Errorf("Must have a oauth1 consumer key file") } - keyfile, err := ioutil.ReadFile(bb.ConsumerRSA) + keyFile, err := ioutil.ReadFile(opts.ConsumerRSA) if err != nil { return nil, err } - block, _ := pem.Decode(keyfile) - bb.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + block, _ := pem.Decode(keyFile) + config.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } - - // TODO de-referencing is a bit weird and may not behave as expected, and could - // have race conditions. Instead store the parsed key (I already did this above) - // and then pass the parsed private key when creating the Bitbucket client. - bb.Consumer = *NewClient(bb.ConsumerRSA, bb.ConsumerKey, bb.URL) - return bb, nil + return config, nil } -type client struct { - URL string - ConsumerKey string - GitUserName string - GitPassword string - ConsumerRSA string - PrivateKey *rsa.PrivateKey - Consumer oauth.Consumer -} -func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { - requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob") +func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { + requestToken, url, err := c.Consumer().GetRequestTokenAndUrl("oob") if err != nil { return nil, err } - var code = req.FormValue("oauth_verifier") if len(code) == 0 { http.Redirect(res, req, url, http.StatusSeeOther) return nil, nil } - requestToken.Token = req.FormValue("oauth_token") - accessToken, err := c.Consumer.AuthorizeToken(requestToken, code) + accessToken, err := c.Consumer().AuthorizeToken(requestToken, code) if err != nil { return nil, err } - client, err := c.Consumer.MakeHttpClient(accessToken) - if err != nil { - return nil, err - } + client := internal.NewClientWithToken(c.URL, c.Consumer(), accessToken.Token) - response, err := client.Get(fmt.Sprintf("%s/plugins/servlet/applinks/whoami", c.URL)) - if err != nil { - return nil, err - } - defer response.Body.Close() - bits, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - login := string(bits) + return client.FindCurrentUser() - // TODO errors should never be ignored like this - response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s", c.URL, login)) - if err != nil { - return nil, err - } - defer response1.Body.Close() - contents, err := ioutil.ReadAll(response1.Body) - if err !=nil { - return nil, err - } - - var user User - err = json.Unmarshal(contents, &user) - if err != nil { - return nil, err - } - return &model.User{ - Login: login, - Email: user.EmailAddress, - Token: accessToken.Token, - Avatar: avatarLink(user.EmailAddress), - }, nil } // Auth is not supported by the Stash driver. -func (*client) Auth(token, secret string) (string, error) { +func (*Config) Auth(token, secret string) (string, error) { return "", fmt.Errorf("Not Implemented") } // Teams is not supported by the Stash driver. -func (*client) Teams(u *model.User) ([]*model.Team, error) { +func (*Config) Teams(u *model.User) ([]*model.Team, error) { var teams []*model.Team return teams, nil } -func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { - client := NewClientWithToken(&c.Consumer, u.Token) - repo , err := c.FindRepo(client,owner,name) - if err != nil { - return nil, err - } - return repo, nil +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) } -func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { +func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { - var repos = []*model.RepoLite{} + client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) - client := NewClientWithToken(&c.Consumer, u.Token) - - response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000", c.URL)) - if err != nil { - log.Error(err) - } - defer response.Body.Close() - contents, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - var repoResponse Repos - err = json.Unmarshal(contents, &repoResponse) - if err != nil { - return nil, err - } - - for _, repo := range repoResponse.Values { - repos = append(repos, &model.RepoLite{ - Name: repo.Slug, - FullName: repo.Project.Key + "/" + repo.Slug, - Owner: repo.Project.Key, - }) - } - - return repos, nil + return client.FindRepos() } -func (c *client) Perm(u *model.User, owner, repo string) (*model.Perm, error) { - client := NewClientWithToken(&c.Consumer, u.Token) - perms := new(model.Perm) +func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) { + client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) - // If you don't have access return none right away - _, err := c.FindRepo(client, owner, repo) - if err != nil { - return perms, err - } - - // Must have admin to be able to list hooks. If have access the enable perms - _, err = client.Get(fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s", c.URL, owner, repo,"com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook")) - if err == nil { - perms.Push = true - perms.Admin = true - } - perms.Pull = true - return perms, nil + return client.FindRepoPerms(owner, repo) } -func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { - log.Info(fmt.Sprintf("Staring file for bitbucketServer login: %s repo: %s buildevent: %s string: %s", u.Login, r.Name, b.Event, f)) +func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { - client := NewClientWithToken(&c.Consumer, u.Token) - fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", c.URL, r.Owner, r.Name, f) - log.Info(fileURL) - response, err := client.Get(fileURL) - if err != nil { - log.Error(err) - } - if response.StatusCode == 404 { - return nil, nil - } - defer response.Body.Close() - responseBytes, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Error(err) - } + client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) - return responseBytes, nil + return client.FindFileForRepo(r.Owner, r.Name, f) } // Status is not supported by the Gogs driver. -func (*client) Status(*model.User, *model.Repo, *model.Build, string) error { +func (*Config) Status(*model.User, *model.Repo, *model.Build, string) error { return nil } -func (c *client) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) { +func (c *Config) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) { u, err := url.Parse(c.URL) if err != nil { return nil, err @@ -252,31 +165,23 @@ func (c *client) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) { } return &model.Netrc{ Machine: host, - Login: c.GitUserName, - Password: c.GitPassword, + Login: c.Username, + Password: c.Password, }, nil } -func (c *client) Activate(u *model.User, r *model.Repo, link string) error { - client := NewClientWithToken(&c.Consumer, u.Token) - hook, err := c.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link) - if err != nil { - return err - } - log.Info(hook) - return nil +func (c *Config) Activate(u *model.User, r *model.Repo, link string) error { + client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + + return client.CreateHook(r.Owner, r.Name, link) } -func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error { - client := NewClientWithToken(&c.Consumer, u.Token) - err := c.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link) - if err != nil { - return err - } - return nil +func (c *Config) Deactivate(u *model.User, r *model.Repo, link string) error { + client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + return client.DeleteHook(r.Owner, r.Name, link) } -func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, 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 @@ -301,83 +206,29 @@ func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) { return repo, build, nil } -type HookDetail struct { - Key string `json:"key"` - Name string `json:"name"` - Type string `json:"type"` - Description string `json:"description"` - Version string `json:"version"` - ConfigFormKey string `json:"configFormKey"` + +func (c *Config) Consumer() *oauth.Consumer{ + consumer := oauth.NewRSAConsumer( + c.ConsumerKey, + c.PrivateKey, + oauth.ServiceProvider{ + RequestTokenUrl: fmt.Sprintf(requestTokenURL, c.URL), + AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, c.URL), + AccessTokenUrl: fmt.Sprintf(accessTokenURL, c.URL), + HttpMethod: "POST", + }) + consumer.HttpClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + return consumer } -type Hook struct { - Enabled bool `json:"enabled"` - Details *HookDetail `json:"details"` -} - -// Enable hook for named repository -func (bs *client) CreateHook(client *http.Client, project, slug, hook_key, link string) (*Hook, error) { - - // Set hook - hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, link)) - - // Enable hook - enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled", - project, slug, hook_key) - - doPut(client, bs.URL+enablePath, hookBytes) - - return nil, nil -} - -// Disable hook for named repository -func (bs *client) DeleteHook(client *http.Client, project, slug, hook_key, link string) error { - enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled", - project, slug, hook_key) - doDelete(client, bs.URL+enablePath) - - return nil -} - -func (c *client) FindRepo(client *http.Client, owner string, name string) (*model.Repo, error){ - - urlString := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", c.URL, owner, name) - - response, err := client.Get(urlString) - if err != nil { - log.Error(err) - } - defer response.Body.Close() - contents, err := ioutil.ReadAll(response.Body) - bsRepo := BSRepo{} - err = json.Unmarshal(contents, &bsRepo) - if err !=nil { - return nil, err - } - repo := &model.Repo{ - Name: bsRepo.Slug, - Owner: bsRepo.Project.Key, - Branch: "master", - Kind: model.RepoGit, - IsPrivate: true, // TODO(josmo) possibly set this as a setting - must always be private to use netrc - 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 - } - } - - return repo, nil +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/client.go b/remote/bitbucketserver/client.go deleted file mode 100644 index 6c6f280b6..000000000 --- a/remote/bitbucketserver/client.go +++ /dev/null @@ -1,52 +0,0 @@ -package bitbucketserver - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - log "github.com/Sirupsen/logrus" - "github.com/mrjones/oauth" - "io/ioutil" - "net/http" -) - -func NewClient(ConsumerRSA string, ConsumerKey string, URL string) *oauth.Consumer { - //TODO: make this configurable - privateKeyFileContents, err := ioutil.ReadFile(ConsumerRSA) - log.Info("Tried to read the key") - if err != nil { - log.Error(err) - } - - block, _ := pem.Decode([]byte(privateKeyFileContents)) - privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - log.Error(err) - } - - c := oauth.NewRSAConsumer( - ConsumerKey, - privateKey, - oauth.ServiceProvider{ - RequestTokenUrl: URL + "/plugins/servlet/oauth/request-token", - AuthorizeTokenUrl: URL + "/plugins/servlet/oauth/authorize", - AccessTokenUrl: URL + "/plugins/servlet/oauth/access-token", - HttpMethod: "POST", - }) - c.HttpClient = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - } - return c -} - -func NewClientWithToken(Consumer *oauth.Consumer, AccessToken string) *http.Client { - var token oauth.AccessToken - token.Token = AccessToken - client, err := Consumer.MakeHttpClient(&token) - if err != nil { - log.Error(err) - } - return client -} diff --git a/remote/bitbucketserver/helper.go b/remote/bitbucketserver/helper.go deleted file mode 100644 index 9c3985113..000000000 --- a/remote/bitbucketserver/helper.go +++ /dev/null @@ -1,64 +0,0 @@ -package bitbucketserver - -import ( - "bytes" - "crypto/md5" - "encoding/hex" - "fmt" - log "github.com/Sirupsen/logrus" - "io/ioutil" - "net/http" - "strings" -) - -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) - log.Info(avatarURL) - return avatarURL -} - -func doPut(client *http.Client, url string, body []byte) { - request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) - request.Header.Add("Content-Type", "application/json") - response, err := client.Do(request) - if err != nil { - log.Error(err) - } else { - defer response.Body.Close() - contents, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Error(err) - } - fmt.Println("The calculated length is:", len(string(contents)), "for the url:", url) - fmt.Println(" ", response.StatusCode) - hdr := response.Header - for key, value := range hdr { - fmt.Println(" ", key, ":", value) - } - fmt.Println(string(contents)) - } -} - -func doDelete(client *http.Client, url string) { - request, err := http.NewRequest("DELETE", url, nil) - response, err := client.Do(request) - if err != nil { - log.Error(err) - } else { - defer response.Body.Close() - contents, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Error(err) - } - fmt.Println("The calculated length is:", len(string(contents)), "for the url:", url) - fmt.Println(" ", response.StatusCode) - hdr := response.Header - for key, value := range hdr { - fmt.Println(" ", key, ":", value) - } - fmt.Println(string(contents)) - } -} diff --git a/remote/bitbucketserver/internal/client.go b/remote/bitbucketserver/internal/client.go new file mode 100644 index 000000000..d6ea0243d --- /dev/null +++ b/remote/bitbucketserver/internal/client.go @@ -0,0 +1,228 @@ +package internal + +import ( + "net/http" + log "github.com/Sirupsen/logrus" + "github.com/mrjones/oauth" + "github.com/drone/drone/model" + "fmt" + "io/ioutil" + "encoding/json" + "strings" + "encoding/hex" + "crypto/md5" + "net/url" + "bytes" +) + +const ( + currentUserId = "%s/plugins/servlet/applinks/whoami" + pathUser = "%s/rest/api/1.0/users/%s" + pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" + pathRepos = "%s/rest/api/1.0/repos?limit=%s" + pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" + pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" + hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" + pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled" +) + +type Client struct { + *http.Client + base string + accessToken string +} + +func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string) *Client { + var token oauth.AccessToken + token.Token = AccessToken + client, err := Consumer.MakeHttpClient(&token) + if err != nil { + log.Error(err) + } + return &Client{client, url, AccessToken} +} + +func (c *Client) FindCurrentUser() (*model.User, error) { + CurrentUserIdResponse, err := c.Get(fmt.Sprintf(currentUserId, c.base)) + if err != nil { + return nil, err + } + defer CurrentUserIdResponse.Body.Close() + bits, err := ioutil.ReadAll(CurrentUserIdResponse.Body) + if err != nil { + return nil, err + } + login := string(bits) + + // TODO errors should never be ignored like this + CurrentUserResponse, err := c.Get(fmt.Sprintf(pathUser, c.base, login)) + if err != nil { + return nil, err + } + defer CurrentUserResponse.Body.Close() + + contents, err := ioutil.ReadAll(CurrentUserResponse.Body) + if err !=nil { + return nil, err + } + + var user User + err = json.Unmarshal(contents, &user) + if err != nil { + return nil, err + } + return &model.User{ + Login: login, + Email: user.EmailAddress, + Token: c.accessToken, + Avatar: avatarLink(user.EmailAddress), + }, nil + +} + +func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ + urlString := fmt.Sprintf(pathRepo, c.base, owner, name) + response, err := c.Get(urlString) + if err != nil { + log.Error(err) + } + defer response.Body.Close() + contents, err := ioutil.ReadAll(response.Body) + bsRepo := BSRepo{} + err = json.Unmarshal(contents, &bsRepo) + 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 + } + } + + return repo, nil +} + +func (c *Client) FindRepos() ([]*model.RepoLite, error) { + response, err := c.Get(fmt.Sprintf(pathRepos, c.base)) + if err != nil { + log.Error(err) + } + defer response.Body.Close() + contents, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + var repoResponse Repos + err = json.Unmarshal(contents, &repoResponse) + if err != nil { + return nil, err + } + 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, + }) + } + + return repos, nil +} + +func (c *Client) FindRepoPerms(owner string, repo string) (*model.Perm, error) { + perms := new(model.Perm) + // If you don't have access return none right away + _, err := c.FindRepo(owner, repo) + if err != nil { + return perms, err + } + // Must have admin to be able to list hooks. If have access the enable perms + _, err = c.Get(fmt.Sprintf(pathHook, c.base, owner, repo,hookName)) + if err == nil { + perms.Push = true + perms.Admin = true + } + perms.Pull = true + return perms, nil +} + +func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([]byte, error) { + response, err := c.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) + if err != nil { + log.Error(err) + } + if response.StatusCode == 404 { + return nil, nil + } + defer response.Body.Close() + responseBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Error(err) + } + return responseBytes, nil +} + +func(c *Client) CreateHook(owner string, name string, callBackLink string) error { + // Set hook + //TODO: Check existing and add up to 5 + hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, callBackLink)) + return c.doPut(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName), hookBytes) +} + +func(c *Client) DeleteHook(owner string, name string, link string) error { + //TODO: eventially should only delete the link callback + return c.doDelete(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName )) +} + +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) + log.Info(avatarURL) + return avatarURL +} + + +//Helper function to help create the hook +func(c *Client) doPut(url string, body []byte) error { + request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) + request.Header.Add("Content-Type", "application/json") + response, err := c.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + return nil + +} + +//Helper function to do delete on the hook +func(c *Client) doDelete(url string) error { + request, err := http.NewRequest("DELETE", url, nil) + response, err := c.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + return nil +} \ No newline at end of file diff --git a/remote/bitbucketserver/internal/types.go b/remote/bitbucketserver/internal/types.go new file mode 100644 index 000000000..ef21208c6 --- /dev/null +++ b/remote/bitbucketserver/internal/types.go @@ -0,0 +1,104 @@ +package internal + + +type User struct { + Active bool `json:"active"` + DisplayName string `json:"displayName"` + EmailAddress string `json:"emailAddress"` + ID int `json:"id"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + Name string `json:"name"` + Slug string `json:"slug"` + Type string `json:"type"` +} + + +type BSRepo 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"` +} + +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"` +} + +type Hook struct { + Enabled bool `json:"enabled"` + Details *HookDetail `json:"details"` +} + +type HookDetail struct { + Key string `json:"key"` + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` + Version string `json:"version"` + ConfigFormKey string `json:"configFormKey"` +} \ No newline at end of file diff --git a/remote/bitbucketserver/types.go b/remote/bitbucketserver/types.go index 3a1fb9083..248b8c5ef 100644 --- a/remote/bitbucketserver/types.go +++ b/remote/bitbucketserver/types.go @@ -84,59 +84,9 @@ type postHook struct { } `json:"repository"` } -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"` -} -type User struct { - Active bool `json:"active"` - DisplayName string `json:"displayName"` - EmailAddress string `json:"emailAddress"` - ID int `json:"id"` - Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` - Name string `json:"name"` - Slug string `json:"slug"` - Type string `json:"type"` -} + + type BSRepo struct { Forkable bool `json:"forkable"` From 6e4303aab3f8de8e039c623db3a768b3f9fb939a Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sat, 25 Jun 2016 22:27:09 -0700 Subject: [PATCH 2/9] Update to cleaner implementation for the bitbucket server implementation --- remote/bitbucketserver/bitbucketserver.go | 85 +++++++++----------- remote/bitbucketserver/internal/client.go | 98 ++++++++++++----------- remote/bitbucketserver/internal/types.go | 92 +++++++++++---------- remote/bitbucketserver/types.go | 4 - 4 files changed, 137 insertions(+), 142 deletions(-) diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 93dd50dc1..5b6000ca9 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -4,32 +4,31 @@ 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" + log "github.com/Sirupsen/logrus" + "github.com/drone/drone/model" + "github.com/drone/drone/remote" + "github.com/drone/drone/remote/bitbucketserver/internal" + "github.com/mrjones/oauth" "io/ioutil" "net/http" "net/url" - "github.com/drone/drone/remote/bitbucketserver/internal" - "github.com/drone/drone/model" - "github.com/drone/drone/remote" - "github.com/mrjones/oauth" "strings" - "crypto/tls" - "encoding/hex" - "crypto/md5" ) const ( - requestTokenURL = "%s/plugins/servlet/oauth/request-token" + requestTokenURL = "%s/plugins/servlet/oauth/request-token" authorizeTokenURL = "%s/plugins/servlet/oauth/authorize" - accessTokenURL = "%s/plugins/servlet/oauth/access-token" + accessTokenURL = "%s/plugins/servlet/oauth/access-token" ) - - // Opts defines configuration options. type Opts struct { URL string // Stash server url. @@ -37,27 +36,24 @@ type Opts struct { Password string // Git machine account password. ConsumerKey string // Oauth1 consumer key. ConsumerRSA string // Oauth1 consumer key file. - SkipVerify bool // Skip ssl verification. + SkipVerify bool // Skip ssl verification. } type Config struct { - URL string - Username string - Password string - PrivateKey *rsa.PrivateKey - ConsumerKey string + URL string + Username string + Password string SkipVerify bool - + Consumer *oauth.Consumer } // New returns a Remote implementation that integrates with Bitbucket Server, // the on-premise edition of Bitbucket Cloud, formerly known as Stash. func New(opts Opts) (remote.Remote, error) { config := &Config{ - URL: opts.URL, - Username: opts.Username, - Password: opts.Password, - ConsumerKey: opts.ConsumerKey, + URL: opts.URL, + Username: opts.Username, + Password: opts.Password, SkipVerify: opts.SkipVerify, } @@ -77,16 +73,16 @@ func New(opts Opts) (remote.Remote, error) { return nil, err } block, _ := pem.Decode(keyFile) - config.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + PrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } + config.Consumer = CreateConsumer(opts.URL, opts.ConsumerKey, PrivateKey) return config, nil } - func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { - requestToken, url, err := c.Consumer().GetRequestTokenAndUrl("oob") + requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob") if err != nil { return nil, err } @@ -96,16 +92,15 @@ func (c *Config) Login(res http.ResponseWriter, req *http.Request) (*model.User, return nil, nil } requestToken.Token = req.FormValue("oauth_token") - accessToken, err := c.Consumer().AuthorizeToken(requestToken, code) + accessToken, err := c.Consumer.AuthorizeToken(requestToken, code) if err != nil { return nil, err } - client := internal.NewClientWithToken(c.URL, c.Consumer(), accessToken.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, accessToken.Token) return client.FindCurrentUser() - } // Auth is not supported by the Stash driver. @@ -120,33 +115,34 @@ 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) + log.Debug(fmt.Printf("Start repo lookup with: %+v %s %s\n", u, owner, name)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepo(owner, name) } func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { - - client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start repos lookup for: %+v\n", u)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepos() } func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) { - client := internal.NewClientWithToken(c.URL,c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start perm lookup for: %+v %s %s\n", u, owner, repo)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepoPerms(owner, repo) } func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { - - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + log.Debug(fmt.Printf("Start file lookup for: %+v %+v %s\n", u, b, f)) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindFileForRepo(r.Owner, r.Name, f) } -// Status is not supported by the Gogs driver. +// Status is not supported by the bitbucketserver driver. func (*Config) Status(*model.User, *model.Repo, *model.Build, string) error { return nil } @@ -171,13 +167,13 @@ func (c *Config) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) { } func (c *Config) Activate(u *model.User, r *model.Repo, link string) error { - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.CreateHook(r.Owner, r.Name, link) } func (c *Config) Deactivate(u *model.User, r *model.Repo, link string) error { - client := internal.NewClientWithToken(c.URL, c.Consumer(), u.Token) + client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.DeleteHook(r.Owner, r.Name, link) } @@ -206,15 +202,14 @@ func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { return repo, build, nil } - -func (c *Config) Consumer() *oauth.Consumer{ +func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey) *oauth.Consumer { consumer := oauth.NewRSAConsumer( - c.ConsumerKey, - c.PrivateKey, + ConsumerKey, + PrivateKey, oauth.ServiceProvider{ - RequestTokenUrl: fmt.Sprintf(requestTokenURL, c.URL), - AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, c.URL), - AccessTokenUrl: fmt.Sprintf(accessTokenURL, c.URL), + RequestTokenUrl: fmt.Sprintf(requestTokenURL, URL), + AuthorizeTokenUrl: fmt.Sprintf(authorizeTokenURL, URL), + AccessTokenUrl: fmt.Sprintf(accessTokenURL, URL), HttpMethod: "POST", }) consumer.HttpClient = &http.Client{ diff --git a/remote/bitbucketserver/internal/client.go b/remote/bitbucketserver/internal/client.go index d6ea0243d..657bd34ed 100644 --- a/remote/bitbucketserver/internal/client.go +++ b/remote/bitbucketserver/internal/client.go @@ -1,34 +1,34 @@ package internal import ( - "net/http" - log "github.com/Sirupsen/logrus" - "github.com/mrjones/oauth" - "github.com/drone/drone/model" - "fmt" - "io/ioutil" - "encoding/json" - "strings" - "encoding/hex" - "crypto/md5" - "net/url" "bytes" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + log "github.com/Sirupsen/logrus" + "github.com/drone/drone/model" + "github.com/mrjones/oauth" + "io/ioutil" + "net/http" + "net/url" + "strings" ) const ( - currentUserId = "%s/plugins/servlet/applinks/whoami" - pathUser = "%s/rest/api/1.0/users/%s" - pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" - pathRepos = "%s/rest/api/1.0/repos?limit=%s" - pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" - pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" - hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" + currentUserId = "%s/plugins/servlet/applinks/whoami" + pathUser = "%s/rest/api/1.0/users/%s" + pathRepo = "%s/rest/api/1.0/projects/%s/repos/%s" + pathRepos = "%s/rest/api/1.0/repos?limit=%s" + pathHook = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s" + pathSource = "%s/projects/%s/repos/%s/browse/%s?raw" + hookName = "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook" pathHookEnabled = "%s/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled" ) type Client struct { - *http.Client - base string + client *http.Client + base string accessToken string } @@ -36,6 +36,7 @@ func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string var token oauth.AccessToken token.Token = AccessToken client, err := Consumer.MakeHttpClient(&token) + log.Debug(fmt.Printf("Create client: %+v %s\n", token, url)) if err != nil { log.Error(err) } @@ -43,7 +44,7 @@ func NewClientWithToken(url string, Consumer *oauth.Consumer, AccessToken string } func (c *Client) FindCurrentUser() (*model.User, error) { - CurrentUserIdResponse, err := c.Get(fmt.Sprintf(currentUserId, c.base)) + CurrentUserIdResponse, err := c.client.Get(fmt.Sprintf(currentUserId, c.base)) if err != nil { return nil, err } @@ -54,15 +55,14 @@ func (c *Client) FindCurrentUser() (*model.User, error) { } login := string(bits) - // TODO errors should never be ignored like this - CurrentUserResponse, err := c.Get(fmt.Sprintf(pathUser, c.base, login)) + CurrentUserResponse, err := c.client.Get(fmt.Sprintf(pathUser, c.base, login)) if err != nil { return nil, err } defer CurrentUserResponse.Body.Close() contents, err := ioutil.ReadAll(CurrentUserResponse.Body) - if err !=nil { + if err != nil { return nil, err } @@ -71,18 +71,21 @@ func (c *Client) FindCurrentUser() (*model.User, error) { if err != nil { return nil, err } - return &model.User{ + + ModelUser := &model.User{ Login: login, Email: user.EmailAddress, Token: c.accessToken, Avatar: avatarLink(user.EmailAddress), - }, nil + } + log.Debug(fmt.Printf("User information: %+v\n", ModelUser)) + return ModelUser, nil } -func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ +func (c *Client) FindRepo(owner string, name string) (*model.Repo, error) { urlString := fmt.Sprintf(pathRepo, c.base, owner, name) - response, err := c.Get(urlString) + response, err := c.client.Get(urlString) if err != nil { log.Error(err) } @@ -90,7 +93,7 @@ func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ contents, err := ioutil.ReadAll(response.Body) bsRepo := BSRepo{} err = json.Unmarshal(contents, &bsRepo) - if err !=nil { + if err != nil { return nil, err } repo := &model.Repo{ @@ -117,14 +120,16 @@ func (c *Client) FindRepo(owner string, name string) (*model.Repo, error){ repo.Link = item.Href } } - + log.Debug(fmt.Printf("Repo: %+v\n", repo)) return repo, nil } func (c *Client) FindRepos() ([]*model.RepoLite, error) { - response, err := c.Get(fmt.Sprintf(pathRepos, c.base)) + requestUrl := fmt.Sprintf(pathRepos, c.base, "1000") + log.Debug(fmt.Printf("request :%s", requestUrl)) + response, err := c.client.Get(requestUrl) if err != nil { - log.Error(err) + return nil, err } defer response.Body.Close() contents, err := ioutil.ReadAll(response.Body) @@ -136,6 +141,7 @@ func (c *Client) FindRepos() ([]*model.RepoLite, error) { if err != nil { 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{ @@ -144,7 +150,7 @@ func (c *Client) FindRepos() ([]*model.RepoLite, error) { Owner: repo.Project.Key, }) } - + log.Debug(fmt.Printf("repos: %+v\n", repos)) return repos, nil } @@ -156,17 +162,18 @@ func (c *Client) FindRepoPerms(owner string, repo string) (*model.Perm, error) { return perms, err } // Must have admin to be able to list hooks. If have access the enable perms - _, err = c.Get(fmt.Sprintf(pathHook, c.base, owner, repo,hookName)) + _, err = c.client.Get(fmt.Sprintf(pathHook, c.base, owner, repo, hookName)) if err == nil { perms.Push = true perms.Admin = true } perms.Pull = true + log.Debug(fmt.Printf("Perms: %+v\n", perms)) return perms, nil } func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([]byte, error) { - response, err := c.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) + response, err := c.client.Get(fmt.Sprintf(pathSource, c.base, owner, repo, fileName)) if err != nil { log.Error(err) } @@ -181,16 +188,16 @@ func (c *Client) FindFileForRepo(owner string, repo string, fileName string) ([] return responseBytes, nil } -func(c *Client) CreateHook(owner string, name string, callBackLink string) error { +func (c *Client) CreateHook(owner string, name string, callBackLink string) error { // Set hook //TODO: Check existing and add up to 5 hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, callBackLink)) - return c.doPut(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName), hookBytes) + return c.doPut(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName), hookBytes) } -func(c *Client) DeleteHook(owner string, name string, link string) error { +func (c *Client) DeleteHook(owner string, name string, link string) error { //TODO: eventially should only delete the link callback - return c.doDelete(fmt.Sprintf(pathHookEnabled,c.base, owner, name, hookName )) + return c.doDelete(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName)) } func avatarLink(email string) (url string) { @@ -198,16 +205,15 @@ func avatarLink(email string) (url string) { 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.Info(avatarURL) + log.Debug(avatarURL) return avatarURL } - //Helper function to help create the hook -func(c *Client) doPut(url string, body []byte) error { +func (c *Client) doPut(url string, body []byte) error { request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) request.Header.Add("Content-Type", "application/json") - response, err := c.Do(request) + response, err := c.client.Do(request) if err != nil { return err } @@ -217,12 +223,12 @@ func(c *Client) doPut(url string, body []byte) error { } //Helper function to do delete on the hook -func(c *Client) doDelete(url string) error { +func (c *Client) doDelete(url string) error { request, err := http.NewRequest("DELETE", url, nil) - response, err := c.Do(request) + response, err := c.client.Do(request) if err != nil { return err } defer response.Body.Close() return nil -} \ No newline at end of file +} diff --git a/remote/bitbucketserver/internal/types.go b/remote/bitbucketserver/internal/types.go index ef21208c6..ac222043b 100644 --- a/remote/bitbucketserver/internal/types.go +++ b/remote/bitbucketserver/internal/types.go @@ -1,48 +1,46 @@ package internal - type User struct { Active bool `json:"active"` DisplayName string `json:"displayName"` EmailAddress string `json:"emailAddress"` ID int `json:"id"` Links struct { - Self []struct { - Href string `json:"href"` - } `json:"self"` - } `json:"links"` + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` Name string `json:"name"` Slug string `json:"slug"` Type string `json:"type"` } - type BSRepo 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"` + 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"` + 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"` @@ -59,28 +57,28 @@ type Repos 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"` + 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"` + 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"` @@ -101,4 +99,4 @@ type HookDetail struct { Description string `json:"description"` Version string `json:"version"` ConfigFormKey string `json:"configFormKey"` -} \ No newline at end of file +} diff --git a/remote/bitbucketserver/types.go b/remote/bitbucketserver/types.go index 248b8c5ef..d4876ad9a 100644 --- a/remote/bitbucketserver/types.go +++ b/remote/bitbucketserver/types.go @@ -84,10 +84,6 @@ type postHook struct { } `json:"repository"` } - - - - type BSRepo struct { Forkable bool `json:"forkable"` ID int `json:"id"` From 3349089d2aa255f0bab4b112b45ec0b98ff9c3f8 Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sat, 25 Jun 2016 22:59:41 -0700 Subject: [PATCH 3/9] Moved struct --- remote/bitbucketserver/types.go | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/remote/bitbucketserver/types.go b/remote/bitbucketserver/types.go index d4876ad9a..9b77600e9 100644 --- a/remote/bitbucketserver/types.go +++ b/remote/bitbucketserver/types.go @@ -84,35 +84,4 @@ type postHook struct { } `json:"repository"` } -type BSRepo 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"` -} + From f4aa0168c7540fe7a347433726f7ab09cb5e6d2c Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 26 Jun 2016 00:10:09 -0700 Subject: [PATCH 4/9] Making sure to have branch name on the build --- remote/bitbucketserver/bitbucketserver.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 5b6000ca9..c50f0f05b 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -182,6 +182,7 @@ func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { if err := json.NewDecoder(r.Body).Decode(hook); err != nil { return nil, nil, err } + log.Debug(fmt.Printf("hook %v", hook)) build := &model.Build{ Event: model.EventPush, @@ -189,6 +190,7 @@ func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { 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{ From f80174f7c4b38b2c6cdcd25be09701f42a4e7074 Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 24 Jul 2016 11:53:11 -0700 Subject: [PATCH 5/9] removing logging from low levels --- remote/bitbucketserver/bitbucketserver.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index c50f0f05b..638bb17ec 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -12,7 +12,6 @@ import ( "encoding/json" "encoding/pem" "fmt" - log "github.com/Sirupsen/logrus" "github.com/drone/drone/model" "github.com/drone/drone/remote" "github.com/drone/drone/remote/bitbucketserver/internal" @@ -115,28 +114,24 @@ func (*Config) Teams(u *model.User) ([]*model.Team, error) { } func (c *Config) Repo(u *model.User, owner, name string) (*model.Repo, error) { - log.Debug(fmt.Printf("Start repo lookup with: %+v %s %s\n", u, owner, name)) client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepo(owner, name) } func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { - log.Debug(fmt.Printf("Start repos lookup for: %+v\n", u)) client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepos() } func (c *Config) Perm(u *model.User, owner, repo string) (*model.Perm, error) { - log.Debug(fmt.Printf("Start perm lookup for: %+v %s %s\n", u, owner, repo)) client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindRepoPerms(owner, repo) } func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { - log.Debug(fmt.Printf("Start file lookup for: %+v %+v %s\n", u, b, f)) client := internal.NewClientWithToken(c.URL, c.Consumer, u.Token) return client.FindFileForRepo(r.Owner, r.Name, f) @@ -182,7 +177,6 @@ func (c *Config) Hook(r *http.Request) (*model.Repo, *model.Build, error) { if err := json.NewDecoder(r.Body).Decode(hook); err != nil { return nil, nil, err } - log.Debug(fmt.Printf("hook %v", hook)) build := &model.Build{ Event: model.EventPush, From 70ebb097c886cf70cfa748e60922d5d9c7d49b51 Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 24 Jul 2016 14:07:44 -0700 Subject: [PATCH 6/9] Changed variables to lowercase Moved to start using conversions and returning "bitbucket server types" Moved the last push type into the internal package. Simplified the types to have values of a repo type --- remote/bitbucketserver/bitbucketserver.go | 61 ++++------- remote/bitbucketserver/convert.go | 97 +++++++++++++++++ remote/bitbucketserver/internal/client.go | 63 ++--------- remote/bitbucketserver/internal/types.go | 127 +++++++++++++++------- remote/bitbucketserver/parse.go | 28 +++++ remote/bitbucketserver/types.go | 87 --------------- 6 files changed, 247 insertions(+), 216 deletions(-) create mode 100644 remote/bitbucketserver/convert.go create mode 100644 remote/bitbucketserver/parse.go delete mode 100644 remote/bitbucketserver/types.go 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"` -} - - From 7a5cf50b3d310ca2c6451c20e6bbb37902b5ad3f Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 24 Jul 2016 14:21:00 -0700 Subject: [PATCH 7/9] Adding the commit message to the build --- remote/bitbucketserver/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remote/bitbucketserver/convert.go b/remote/bitbucketserver/convert.go index 6057a6270..9c329bd66 100644 --- a/remote/bitbucketserver/convert.go +++ b/remote/bitbucketserver/convert.go @@ -65,7 +65,7 @@ func convertPushHook(hook *internal.PostHook) *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 + Message: hook.Changesets.Values[0].ToCommit.Message, //TODO check for index Values 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 From df8f9de33d09cf305435a87057704e36b6d4d959 Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 24 Jul 2016 15:13:50 -0700 Subject: [PATCH 8/9] Support for tags vs heads in builds --- remote/bitbucketserver/convert.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/remote/bitbucketserver/convert.go b/remote/bitbucketserver/convert.go index 9c329bd66..6cd8fd1f9 100644 --- a/remote/bitbucketserver/convert.go +++ b/remote/bitbucketserver/convert.go @@ -7,9 +7,9 @@ import ( log "github.com/Sirupsen/logrus" "github.com/drone/drone/model" "github.com/drone/drone/remote/bitbucketserver/internal" + "github.com/mrjones/oauth" "net/url" "strings" - "github.com/mrjones/oauth" ) // convertRepo is a helper function used to convert a Bitbucket server repository @@ -61,18 +61,28 @@ func convertRepoLite(from *internal.Repo) *model.RepoLite { // 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 { + //get the ref parts to see if it's a tags or heads + refParts := strings.Split(hook.RefChanges[0].RefID, "/") + name := refParts[2] + commitType := refParts[1] + 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 + Branch: name, Message: hook.Changesets.Values[0].ToCommit.Message, //TODO check for index Values - Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress), - Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values + 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 - + Ref: hook.RefChanges[0].RefID, // TODO check for index Values } + switch commitType { + case "tags": + build.Event = model.EventTag + default: + build.Event = model.EventPush + } + return build } From 5b52ef924f78aaaad0e89a051001207f173a62ab Mon Sep 17 00:00:00 2001 From: Joachim Hill-Grannec Date: Sun, 24 Jul 2016 17:06:44 -0700 Subject: [PATCH 9/9] No longer used function --- remote/bitbucketserver/internal/client.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/remote/bitbucketserver/internal/client.go b/remote/bitbucketserver/internal/client.go index 921b24206..3cd0847c1 100644 --- a/remote/bitbucketserver/internal/client.go +++ b/remote/bitbucketserver/internal/client.go @@ -2,8 +2,6 @@ package internal import ( "bytes" - "crypto/md5" - "encoding/hex" "encoding/json" "fmt" log "github.com/Sirupsen/logrus" @@ -11,7 +9,6 @@ import ( "github.com/mrjones/oauth" "io/ioutil" "net/http" - "strings" ) const ( @@ -159,15 +156,6 @@ func (c *Client) DeleteHook(owner string, name string, link string) error { return c.doDelete(fmt.Sprintf(pathHookEnabled, c.base, owner, name, hookName)) } -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) - log.Debug(avatarURL) - return avatarURL -} - //Helper function to help create the hook func (c *Client) doPut(url string, body []byte) error { request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body))