diff --git a/cli/delete.go b/cli/delete.go new file mode 100644 index 000000000..df3be638a --- /dev/null +++ b/cli/delete.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/codegangsta/cli" + "github.com/drone/drone/client" +) + +// NewDeleteCommand returns the CLI command for "delete". +func NewDeleteCommand() cli.Command { + return cli.Command{ + Name: "delete", + Usage: "delete a repository", + Flags: []cli.Flag{}, + Action: func(c *cli.Context) { + handle(c, deleteCommandFunc) + }, + } +} + +// deleteCommandFunc executes the "delete" command. +func deleteCommandFunc(c *cli.Context, client *client.Client) error { + var host, owner, name string + var args = c.Args() + + if len(args) != 0 { + host, owner, name = parseRepo(args[0]) + } + + return client.Repos.Delete(host, owner, name) +} diff --git a/cli/main.go b/cli/main.go index 9b4407943..78935e82c 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,8 +1,9 @@ package main import ( - "github.com/codegangsta/cli" "os" + + "github.com/codegangsta/cli" ) var ( @@ -40,6 +41,7 @@ func main() { NewRestartCommand(), NewWhoamiCommand(), NewSetKeyCommand(), + NewDeleteCommand(), } app.Run(os.Args) diff --git a/client/repos.go b/client/repos.go index 2cd15a10c..29c0d38f4 100644 --- a/client/repos.go +++ b/client/repos.go @@ -32,8 +32,14 @@ func (s *RepoService) Enable(host, owner, name string) error { return s.run("POST", path, nil, nil) } -// DELETE /api/repos/{host}/{owner}/{name} +// POST /api/repos/{host}/{owner}/{name}/deactivate func (s *RepoService) Disable(host, owner, name string) error { + var path = fmt.Sprintf("/api/repos/%s/%s/%s/deactivate", host, owner, name) + return s.run("POST", path, nil, nil) +} + +// DELETE /api/repos/{host}/{owner}/{name}?remove=true +func (s *RepoService) Delete(host, owner, name string) error { var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) return s.run("DELETE", path, nil, nil) } diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go index 8220c9e5b..e29f0d413 100644 --- a/plugin/remote/bitbucket/bitbucket.go +++ b/plugin/remote/bitbucket/bitbucket.go @@ -241,6 +241,25 @@ func (r *Bitbucket) Activate(user *model.User, repo *model.Repo, link string) er return err } +// Deactivate removes a repository by removing all the post-commit hooks +// which are equal to link and removing the SSH deploy key. +func (r *Bitbucket) Deactivate(user *model.User, repo *model.Repo, link string) error { + var client = bitbucket.New( + r.Client, + r.Secret, + user.Access, + user.Secret, + ) + title, err := GetKeyTitle(link) + if err != nil { + return err + } + if err := client.RepoKeys.DeleteName(repo.Owner, repo.Name, title); err != nil { + return err + } + return client.Brokers.DeleteUrl(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost) +} + // ParseHook parses the post-commit hook from the Request body // and returns the required data in a standard format. func (r *Bitbucket) ParseHook(req *http.Request) (*model.Hook, error) { @@ -279,3 +298,13 @@ func (r *Bitbucket) OpenRegistration() bool { func (r *Bitbucket) GetToken(user *model.User) (*model.Token, error) { return nil, nil } + +// GetKeyTitle is a helper function that generates a title for the +// RSA public key based on the username and domain name. +func GetKeyTitle(rawurl string) (string, error) { + var uri, err = url.Parse(rawurl) + if err != nil { + return "", err + } + return fmt.Sprintf("drone@%s", uri.Host), nil +} diff --git a/plugin/remote/github/github.go b/plugin/remote/github/github.go index 6c5bac7a7..4edc71b99 100644 --- a/plugin/remote/github/github.go +++ b/plugin/remote/github/github.go @@ -102,7 +102,7 @@ func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.L return nil, fmt.Errorf("Could not check org membership. %s", err) } if !allowedOrg { - return nil, fmt.Errorf("User does not belong to correct org") + return nil, fmt.Errorf("User does not belong to correct org. Must belong to %v", r.Orgs) } } @@ -169,6 +169,7 @@ func (r *GitHub) GetRepos(user *model.User) ([]*model.Repo, error) { if r.Private || repo.Private { repo.CloneURL = *item.SSHURL + repo.Private = true } // if no permissions we should skip the repository @@ -193,6 +194,23 @@ func (r *GitHub) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) return GetFile(client, repo.Owner, repo.Name, ".drone.yml", hook.Sha) } +// Deactivate removes a repository by removing all the post-commit hooks +// which are equal to link and removing the SSH deploy key. +func (r *GitHub) Deactivate(user *model.User, repo *model.Repo, link string) error { + var client = NewClient(r.API, user.Access, r.SkipVerify) + var title, err = GetKeyTitle(link) + if err != nil { + return err + } + + // remove the deploy-key if it is installed remote. + if err := DeleteKey(client, repo.Owner, repo.Name, title, repo.PublicKey); err != nil { + return err + } + + return DeleteHook(client, repo.Owner, repo.Name, link) +} + // Activate activates a repository by adding a Post-commit hook and // a Public Deploy key, if applicable. func (r *GitHub) Activate(user *model.User, repo *model.Repo, link string) error { diff --git a/plugin/remote/github/helper.go b/plugin/remote/github/helper.go index 4d23180ee..2f05ea0cc 100644 --- a/plugin/remote/github/helper.go +++ b/plugin/remote/github/helper.go @@ -173,6 +173,16 @@ func GetHook(client *github.Client, owner, name, url string) (*github.Hook, erro return nil, nil } +func DeleteHook(client *github.Client, owner, name, url string) error { + hook, err := GetHook(client, owner, name, url) + if err != nil { + return err + } + + _, err = client.Repositories.DeleteHook(owner, name, *hook.ID) + return err +} + // CreateHook is a heper function that creates a post-commit hook // for the specified repository. func CreateHook(client *github.Client, owner, name, url string) (*github.Hook, error) { @@ -230,7 +240,18 @@ func GetKeyTitle(rawurl string) (string, error) { return fmt.Sprintf("drone@%s", uri.Host), nil } -// CreateKey is a heper function that creates a deploy key +// DeleteKey is a helper function that deletes a deploy key +// for the specified repository. +func DeleteKey(client *github.Client, owner, name, title, key string) error { + var k, err = GetKey(client, owner, name, title) + if err != nil { + return err + } + _, err = client.Repositories.DeleteKey(owner, name, *k.ID) + return err +} + +// CreateKey is a helper function that creates a deploy key // for the specified repository. func CreateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) { var k = new(github.Key) @@ -240,7 +261,7 @@ func CreateKey(client *github.Client, owner, name, title, key string) (*github.K return created, err } -// CreateUpdateKey is a heper function that creates a deployment key +// CreateUpdateKey is a helper function that creates a deployment key // for the specified repository if it does not already exist, otherwise // it updates the existing key func CreateUpdateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) { diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 2aaefca7f..6bdbd0de8 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "code.google.com/p/goauth2/oauth" @@ -179,6 +180,41 @@ func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error return client.AddProjectHook(path, link, true, false, true) } +// Deactivate removes a repository by removing all the post-commit hooks +// which are equal to link and removing the SSH deploy key. +func (r *Gitlab) Deactivate(user *model.User, repo *model.Repo, link string) error { + var client = NewClient(r.url, user.Access, r.SkipVerify) + var path = ns(repo.Owner, repo.Name) + + keys, err := client.ProjectDeployKeys(path) + if err != nil { + return err + } + var pubkey = strings.TrimSpace(repo.PublicKey) + for _, k := range keys { + if pubkey == strings.TrimSpace(k.Key) { + if err := client.RemoveProjectDeployKey(path, strconv.Itoa(k.Id)); err != nil { + return err + } + break + } + } + hooks, err := client.ProjectHooks(path) + if err != nil { + return err + } + link += "?owner=" + repo.Owner + "&name=" + repo.Name + for _, h := range hooks { + if link == h.Url { + if err := client.RemoveProjectHook(path, strconv.Itoa(h.Id)); err != nil { + return err + } + break + } + } + return nil +} + // ParseHook parses the post-commit hook from the Request body // and returns the required data in a standard format. func (r *Gitlab) ParseHook(req *http.Request) (*model.Hook, error) { diff --git a/plugin/remote/gogs/gogs.go b/plugin/remote/gogs/gogs.go index edc71893d..73038a4dc 100644 --- a/plugin/remote/gogs/gogs.go +++ b/plugin/remote/gogs/gogs.go @@ -153,6 +153,12 @@ func (r *Gogs) Activate(user *model.User, repo *model.Repo, link string) error { return err } +// Deactivate removes a repository by removing all the post-commit hooks +// which are equal to link and removing the SSH deploy key. +func (r *Gogs) Deactivate(user *model.User, repo *model.Repo, link string) error { + return fmt.Errorf("Remove %#v in gogs not implemented", *repo) +} + // ParseHook parses the post-commit hook from the Request body // and returns the required data in a standard format. func (r *Gogs) ParseHook(req *http.Request) (*model.Hook, error) { diff --git a/plugin/remote/remote.go b/plugin/remote/remote.go index 0d7ad2dc4..862e8c15d 100644 --- a/plugin/remote/remote.go +++ b/plugin/remote/remote.go @@ -29,6 +29,10 @@ type Remote interface { // adding the SSH deploy key, if applicable. Activate(user *model.User, repo *model.Repo, link string) error + // Deactivate removes a repository by removing all the post-commit hooks + // which are equal to link and removing the SSH deploy key. + Deactivate(user *model.User, repo *model.Repo, link string) error + // ParseHook parses the post-commit hook from the Request body // and returns the required data in a standard format. ParseHook(r *http.Request) (*model.Hook, error) diff --git a/server/app/index.html b/server/app/index.html index 755b059da..31b95a09f 100644 --- a/server/app/index.html +++ b/server/app/index.html @@ -29,6 +29,7 @@ + diff --git a/server/app/scripts/app.js b/server/app/scripts/app.js index 4f1a670b0..097e74aca 100644 --- a/server/app/scripts/app.js +++ b/server/app/scripts/app.js @@ -2,9 +2,14 @@ var app = angular.module('app', [ 'ngRoute', - 'ui.filters' + 'ui.filters', + 'angularMoment' ]); +angular.module('app').constant('angularMomentConfig', { + preprocess: 'unix' +}); + // First, parse the query string var params = {}, queryString = location.hash.substring(1), regex = /([^&=]+)=([^&]*)/g, m; diff --git a/server/app/scripts/filters/filters.js b/server/app/scripts/filters/filters.js index 32c1c0886..2100369bd 100644 --- a/server/app/scripts/filters/filters.js +++ b/server/app/scripts/filters/filters.js @@ -25,7 +25,7 @@ /** * toDate is a helper function that returns a human readable - * string gor the given unix date. + * string for the given unix date. */ function toDate() { return function(date) { diff --git a/server/app/views/commit.html b/server/app/views/commit.html index 2cfdfd80b..787b1241e 100644 --- a/server/app/views/commit.html +++ b/server/app/views/commit.html @@ -8,14 +8,14 @@
-

{{ commit.duration | toDuration}}

+

{{ commit.duration | amDurationFormat:'seconds':false }}

-
{{ commit.finished_at | fromNow }}
-
Started {{ commit.started_at | fromNow }}
-
Created {{ commit.created_at}}
+
+
Started
+
Created
diff --git a/server/app/views/repo.html b/server/app/views/repo.html index bd0328a0a..474ca39b2 100644 --- a/server/app/views/repo.html +++ b/server/app/views/repo.html @@ -70,11 +70,11 @@ {{ commit.sha | shortHash }} ({{ commit.branch }}) {{ commit.author }} - {{ commit.duration | toDuration }} - {{ commit.started_at | fromNow }} - - + {{ commit.duration | amDurationFormat:'seconds':false }} + + - {{ commit.finished_at | fromNow }} + - @@ -88,24 +88,24 @@

{{ commit.message }}

- {{ commit.author }}
{{ commit.finished_at | fromNow }}
- {{ commit.author }}
Started {{ commit.started_at | fromNow }}
- {{ commit.author }}
Created {{ commit.created_at}}
+ {{ commit.author }}
+ {{ commit.author }}
Started
+ {{ commit.author }}
Created
-
+

Pull Requests

{{ commit.message }}

- {{ commit.author }}
{{ commit.finished_at | fromNow }}
- {{ commit.author }}
Started {{ commit.started_at | fromNow }}
- {{ commit.author }}
Created {{ commit.created_at}}
+ {{ commit.author }}
+ {{ commit.author }}
Started
+ {{ commit.author }}
Created
diff --git a/server/handler/repo.go b/server/handler/repo.go index 2c89f6071..98c79f4d6 100644 --- a/server/handler/repo.go +++ b/server/handler/repo.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "fmt" + "log" "net/http" "github.com/drone/drone/plugin/remote" @@ -42,9 +43,8 @@ func GetRepo(c web.C, w http.ResponseWriter, r *http.Request) { }{repo, repo.PublicKey, repo.Params, role}) } -// DelRepo accepts a request to inactivate the named -// repository. This will disable all builds in the system -// for this repository. +// DelRepo accepts a request to delete the named +// repository. // // DEL /api/repos/:host/:owner/:name // @@ -52,6 +52,50 @@ func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var repo = ToRepo(c) + // completely remove the repository from the database + var user = ToUser(c) + var remote = remote.Lookup(repo.Host) + if remote == nil { + log.Printf("[ERROR] no remote for host '%s' found", repo.Host) + } else { + // Request a new token and update + user_token, err := remote.GetToken(user) + if err != nil { + log.Printf("[ERROR] no token for user '%s' on remote '%s' ", user.Email, repo.Host) + } else { + if user_token != nil { + user.Access = user_token.AccessToken + user.Secret = user_token.RefreshToken + user.TokenExpiry = user_token.Expiry + datastore.PutUser(ctx, user) + } + // setup the post-commit hook with the remote system and + // and deactiveate this hook/user on the remote system + var hook = fmt.Sprintf("%s/api/hook/%s/%s", httputil.GetURL(r), repo.Remote, repo.Token) + if err := remote.Deactivate(user, repo, hook); err != nil { + log.Printf("[ERROR] deactivate on remote '%s' failed: %s", repo.Host, err) + } + } + } + // fail through: if any of the actions on the remote failed + // we try to delete the repo in our datastore anyway + if err := datastore.DelRepo(ctx, repo); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } else { + w.WriteHeader(http.StatusNoContent) + } +} + +// DeactivateRepo accepts a request to deactivate the named +// repository. This will disable all builds in the system +// for this repository. +// +// POST /api/repos/:host/:owner/:name/deactivate +// +func DeactivateRepo(c web.C, w http.ResponseWriter, r *http.Request) { + var ctx = context.FromC(c) + var repo = ToRepo(c) + // disable everything repo.Active = false repo.PullRequest = false @@ -62,7 +106,7 @@ func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) return } - w.WriteHeader(http.StatusNoContent) + json.NewEncoder(w).Encode(repo) } // PostRepo accapets a request to activate the named repository diff --git a/server/router/router.go b/server/router/router.go index 0980a8482..b197d0672 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -46,6 +46,7 @@ func New() *web.Mux { repos.Put("/api/repos/:host/:owner/:name", handler.PutRepo) repos.Post("/api/repos/:host/:owner/:name", handler.PostRepo) repos.Delete("/api/repos/:host/:owner/:name", handler.DelRepo) + repos.Post("/api/repos/:host/:owner/:name/deactivate", handler.DeactivateRepo) mux.Handle("/api/repos/:host/:owner/:name", repos) mux.Handle("/api/repos/:host/:owner/:name/*", repos)