diff --git a/cli/delete.go b/cli/delete.go new file mode 100644 index 00000000..df3be638 --- /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 9b440794..78935e82 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 2cd15a10..0b77c15e 100644 --- a/client/repos.go +++ b/client/repos.go @@ -38,6 +38,12 @@ func (s *RepoService) Disable(host, owner, name string) error { return s.run("DELETE", 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?remove=true", host, owner, name) + return s.run("DELETE", path, nil, nil) +} + // PUT /api/repos/{host}/{owner}/{name} func (s *RepoService) SetKey(host, owner, name, pub, priv string) error { var path = fmt.Sprintf("/api/repos/%s/%s/%s", host, owner, name) diff --git a/plugin/remote/bitbucket/bitbucket.go b/plugin/remote/bitbucket/bitbucket.go index 8220c9e5..2e0b4c8e 100644 --- a/plugin/remote/bitbucket/bitbucket.go +++ b/plugin/remote/bitbucket/bitbucket.go @@ -241,6 +241,12 @@ 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 { + return fmt.Errorf("Remove %#v in bitbucket not implemented", *repo) +} + // 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) { diff --git a/plugin/remote/github/github.go b/plugin/remote/github/github.go index 6c5bac7a..ec923203 100644 --- a/plugin/remote/github/github.go +++ b/plugin/remote/github/github.go @@ -193,6 +193,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 4d23180e..257bdfb8 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,26 @@ 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 = new(github.Key) + k.Title = github.String(title) + k.Key = github.String(key) + keys, _, err := client.Repositories.ListKeys(owner, name, nil) + if err != nil { + return err + } + for _, rk := range keys { + if rk.Key != nil && rk.Key == k.Key { + _, err = client.Repositories.DeleteKey(owner, name, *rk.ID) + return err + } + } + return fmt.Errorf("%s not found in the list of keys", title) +} + +// 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 +269,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 23a15d43..9bbd3ce9 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,42 @@ 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) { + fmt.Printf("remove deploy key: %+v\n", k) + 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 edc71893..af81015f 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 gots 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 0d7ad2dc..862e8c15 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/handler/repo.go b/server/handler/repo.go index 5b2048a5..2209cc50 100644 --- a/server/handler/repo.go +++ b/server/handler/repo.go @@ -52,6 +52,37 @@ func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var repo = ToRepo(c) + var rm = r.FormValue("remove") // using ?remove=true + + // completely remove the repository from the database + if len(rm) != 0 { + var user = ToUser(c) + var remote = remote.Lookup(repo.Host) + if remote == nil { + w.WriteHeader(http.StatusNotFound) + return + } + + // Request a new token and update + user_token, err := remote.GetToken(user) + if user_token != nil { + user.Access = user_token.AccessToken + user.Secret = user_token.RefreshToken + datastore.PutUser(ctx, user) + } else if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + // setup the post-commit hook with the remote system and + // if necessary, register the public key + 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 { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + // disable everything repo.Active = false repo.PullRequest = false