diff --git a/drone/server/server.go b/drone/server/server.go index 6fa45da06..8d96921bf 100644 --- a/drone/server/server.go +++ b/drone/server/server.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "net/url" + "strings" "time" "golang.org/x/crypto/acme/autocert" @@ -225,6 +226,37 @@ var Command = cli.Command{ Name: "gogs-skip-verify", Usage: "gogs skip ssl verification", }, + cli.BoolFlag{ + EnvVar: "DRONE_GITEA", + Name: "gitea", + Usage: "gitea driver is enabled", + }, + cli.StringFlag{ + EnvVar: "DRONE_GITEA_URL", + Name: "gitea-server", + Usage: "gitea server address", + Value: "https://try.gitea.io", + }, + cli.StringFlag{ + EnvVar: "DRONE_GITEA_GIT_USERNAME", + Name: "gitea-git-username", + Usage: "gitea service account username", + }, + cli.StringFlag{ + EnvVar: "DRONE_GITEA_GIT_PASSWORD", + Name: "gitea-git-password", + Usage: "gitea service account password", + }, + cli.BoolFlag{ + EnvVar: "DRONE_GITEA_PRIVATE_MODE", + Name: "gitea-private-mode", + Usage: "gitea private mode enabled", + }, + cli.BoolFlag{ + EnvVar: "DRONE_GITEA_SKIP_VERIFY", + Name: "gitea-skip-verify", + Usage: "gitea skip ssl verification", + }, cli.BoolFlag{ EnvVar: "DRONE_BITBUCKET", Name: "bitbucket", @@ -415,7 +447,7 @@ func setupEvilGlobals(c *cli.Context, v store.Store) { droneserver.Config.Server.Cert = c.String("server-cert") droneserver.Config.Server.Key = c.String("server-key") droneserver.Config.Server.Pass = c.String("agent-secret") - droneserver.Config.Server.Host = c.String("server-host") + droneserver.Config.Server.Host = strings.TrimRight(c.String("server-host"), "/") droneserver.Config.Server.Port = c.String("server-addr") droneserver.Config.Pipeline.Networks = c.StringSlice("network") droneserver.Config.Pipeline.Volumes = c.StringSlice("volume") diff --git a/remote/gitea/fixtures/handler.go b/remote/gitea/fixtures/handler.go new file mode 100644 index 000000000..ca6d319af --- /dev/null +++ b/remote/gitea/fixtures/handler.go @@ -0,0 +1,141 @@ +package fixtures + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// Handler returns an http.Handler that is capable of handling a variety of mock +// Gitea requests and returning mock responses. +func Handler() http.Handler { + gin.SetMode(gin.TestMode) + + e := gin.New() + e.GET("/api/v1/repos/:owner/:name", getRepo) + e.GET("/api/v1/repos/:owner/:name/raw/:commit/:file", getRepoFile) + e.POST("/api/v1/repos/:owner/:name/hooks", createRepoHook) + e.GET("/api/v1/repos/:owner/:name/hooks", listRepoHooks) + e.DELETE("/api/v1/repos/:owner/:name/hooks/:id", deleteRepoHook) + e.POST("/api/v1/repos/:owner/:name/statuses/:commit", createRepoCommitStatus) + e.GET("/api/v1/user/repos", getUserRepos) + + return e +} + +func listRepoHooks(c *gin.Context) { + c.String(200, listRepoHookPayloads) +} + +func getRepo(c *gin.Context) { + switch c.Param("name") { + case "repo_not_found": + c.String(404, "") + default: + c.String(200, repoPayload) + } +} + +func createRepoCommitStatus(c *gin.Context) { + if c.Param("commit") == "v1.0.0" || c.Param("commit") == "9ecad50" { + c.String(200, repoPayload) + } + c.String(404, "") +} + +func getRepoFile(c *gin.Context) { + if c.Param("file") == "file_not_found" { + c.String(404, "") + } + if c.Param("commit") == "v1.0.0" || c.Param("commit") == "9ecad50" { + c.String(200, repoFilePayload) + } + c.String(404, "") +} + +func createRepoHook(c *gin.Context) { + in := struct { + Type string `json:"type"` + Conf struct { + Type string `json:"content_type"` + URL string `json:"url"` + } `json:"config"` + }{} + c.BindJSON(&in) + if in.Type != "gitea" || + in.Conf.Type != "json" || + in.Conf.URL != "http://localhost" { + c.String(500, "") + return + } + + c.String(200, "{}") +} + +func deleteRepoHook(c *gin.Context) { + c.String(200, "{}") +} + +func getUserRepos(c *gin.Context) { + switch c.Request.Header.Get("Authorization") { + case "token repos_not_found": + c.String(404, "") + default: + c.String(200, userRepoPayload) + } +} + +const listRepoHookPayloads = ` +[ + { + "id": 1, + "type": "gitea", + "config": { + "content_type": "json", + "url": "http:\/\/localhost\/hook?access_token=1234567890" + } + } +] +` + +const repoPayload = ` +{ + "owner": { + "login": "test_name", + "email": "octocat@github.com", + "avatar_url": "https:\/\/secure.gravatar.com\/avatar\/8c58a0be77ee441bb8f8595b7f1b4e87" + }, + "full_name": "test_name\/repo_name", + "private": true, + "html_url": "http:\/\/localhost\/test_name\/repo_name", + "clone_url": "http:\/\/localhost\/test_name\/repo_name.git", + "permissions": { + "admin": true, + "push": true, + "pull": true + } +} +` + +const repoFilePayload = `{ platform: linux/amd64 }` + +const userRepoPayload = ` +[ + { + "owner": { + "login": "test_name", + "email": "octocat@github.com", + "avatar_url": "https:\/\/secure.gravatar.com\/avatar\/8c58a0be77ee441bb8f8595b7f1b4e87" + }, + "full_name": "test_name\/repo_name", + "private": true, + "html_url": "http:\/\/localhost\/test_name\/repo_name", + "clone_url": "http:\/\/localhost\/test_name\/repo_name.git", + "permissions": { + "admin": true, + "push": true, + "pull": true + } + } +] +` diff --git a/remote/gitea/fixtures/hooks.go b/remote/gitea/fixtures/hooks.go new file mode 100644 index 000000000..b8ab4d6ad --- /dev/null +++ b/remote/gitea/fixtures/hooks.go @@ -0,0 +1,140 @@ +package fixtures + +// HookPush is a sample Gitea push hook +const HookPush = ` +{ + "ref": "refs/heads/master", + "before": "4b2626259b5a97b6b4eab5e6cca66adb986b672b", + "after": "ef98532add3b2feb7a137426bba1248724367df5", + "compare_url": "http://gitea.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5", + "commits": [ + { + "id": "ef98532add3b2feb7a137426bba1248724367df5", + "message": "bump\n", + "url": "http://gitea.golang.org/gordon/hello-world/commit/ef98532add3b2feb7a137426bba1248724367df5", + "author": { + "name": "Gordon the Gopher", + "email": "gordon@golang.org", + "username": "gordon" + } + } + ], + "repository": { + "id": 1, + "name": "hello-world", + "full_name": "gordon/hello-world", + "html_url": "http://gitea.golang.org/gordon/hello-world", + "ssh_url": "git@gitea.golang.org:gordon/hello-world.git", + "clone_url": "http://gitea.golang.org/gordon/hello-world.git", + "description": "", + "website": "", + "watchers": 1, + "owner": { + "name": "gordon", + "email": "gordon@golang.org", + "username": "gordon" + }, + "private": true + }, + "pusher": { + "name": "gordon", + "email": "gordon@golang.org", + "username": "gordon" + }, + "sender": { + "login": "gordon", + "id": 1, + "avatar_url": "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + } +} +` + +// HookPushTag is a sample Gitea tag hook +const HookPushTag = `{ + "secret": "l26Un7G7HXogLAvsyf2hOA4EMARSTsR3", + "ref": "v1.0.0", + "ref_type": "tag", + "repository": { + "id": 1, + "owner": { + "id": 1, + "username": "gordon", + "full_name": "Gordon the Gopher", + "email": "gordon@golang.org", + "avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + }, + "name": "hello-world", + "full_name": "gordon/hello-world", + "description": "", + "private": true, + "fork": false, + "html_url": "http://gitea.golang.org/gordon/hello-world", + "ssh_url": "git@gitea.golang.org:gordon/hello-world.git", + "clone_url": "http://gitea.golang.org/gordon/hello-world.git", + "default_branch": "master", + "created_at": "2015-10-22T19:32:44Z", + "updated_at": "2016-11-24T13:37:16Z" + }, + "sender": { + "id": 1, + "username": "gordon", + "full_name": "Gordon the Gopher", + "email": "gordon@golang.org", + "avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + } +}` + +// HookPullRequest is a sample pull_request webhook payload +const HookPullRequest = `{ + "action": "opened", + "number": 1, + "pull_request": { + "html_url": "http://gitea.golang.org/gordon/hello-world/pull/1", + "state": "open", + "title": "Update the README with new information", + "body": "please merge", + "user": { + "id": 1, + "username": "gordon", + "full_name": "Gordon the Gopher", + "email": "gordon@golang.org", + "avatar_url": "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + }, + "base_branch": "master", + "base": { + "label": "master", + "ref": "master", + "sha": "9353195a19e45482665306e466c832c46560532d" + }, + "head_branch": "feature/changes", + "head": { + "label": "feature/changes", + "ref": "feature/changes", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" + } + }, + "repository": { + "id": 35129377, + "name": "hello-world", + "full_name": "gordon/hello-world", + "owner": { + "id": 1, + "username": "gordon", + "full_name": "Gordon the Gopher", + "email": "gordon@golang.org", + "avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + }, + "private": true, + "html_url": "http://gitea.golang.org/gordon/hello-world", + "clone_url": "https://gitea.golang.org/gordon/hello-world.git", + "default_branch": "master" + }, + "sender": { + "id": 1, + "login": "gordon", + "username": "gordon", + "full_name": "Gordon the Gopher", + "email": "gordon@golang.org", + "avatar_url": "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87" + } +}` diff --git a/remote/gitea/gitea.go b/remote/gitea/gitea.go new file mode 100644 index 000000000..29fd80ef7 --- /dev/null +++ b/remote/gitea/gitea.go @@ -0,0 +1,357 @@ +package gitea + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "net/url" + + "code.gitea.io/sdk/gitea" + "github.com/drone/drone/model" + "github.com/drone/drone/remote" +) + +// Opts defines configuration options. +type Opts struct { + URL string // Gitea server url. + Username string // Optional machine account username. + Password string // Optional machine account password. + PrivateMode bool // Gitea is running in private mode. + SkipVerify bool // Skip ssl verification. +} + +type client struct { + URL string + Machine string + Username string + Password string + PrivateMode bool + SkipVerify bool +} + +const ( + DescPending = "the build is pending" + DescRunning = "the build is running" + DescSuccess = "the build was successful" + DescFailure = "the build failed" + DescCanceled = "the build canceled" + DescBlocked = "the build is pending approval" + DescDeclined = "the build was rejected" +) + +// getStatus is a helper function that converts a Drone +// status to a Gitea status. +func getStatus(status string) gitea.StatusState { + switch status { + case model.StatusPending, model.StatusBlocked: + return gitea.StatusPending + case model.StatusRunning: + return gitea.StatusPending + case model.StatusSuccess: + return gitea.StatusSuccess + case model.StatusFailure, model.StatusError: + return gitea.StatusFailure + case model.StatusKilled: + return gitea.StatusFailure + case model.StatusDeclined: + return gitea.StatusWarning + default: + return gitea.StatusFailure + } +} + +// getDesc is a helper function that generates a description +// message for the build based on the status. +func getDesc(status string) string { + switch status { + case model.StatusPending: + return DescPending + case model.StatusRunning: + return DescRunning + case model.StatusSuccess: + return DescSuccess + case model.StatusFailure, model.StatusError: + return DescFailure + case model.StatusKilled: + return DescCanceled + case model.StatusBlocked: + return DescBlocked + case model.StatusDeclined: + return DescDeclined + default: + return DescFailure + } +} + +// New returns a Remote implementation that integrates with Gitea, an open +// source Git service written in Go. See https://gitea.io/ +func New(opts Opts) (remote.Remote, error) { + url, err := url.Parse(opts.URL) + if err != nil { + return nil, err + } + host, _, err := net.SplitHostPort(url.Host) + if err == nil { + url.Host = host + } + return &client{ + URL: opts.URL, + Machine: url.Host, + Username: opts.Username, + Password: opts.Password, + PrivateMode: opts.PrivateMode, + SkipVerify: opts.SkipVerify, + }, nil +} + +// Login authenticates an account with Gitea using basic authenticaiton. The +// Gitea account details are returned when the user is successfully authenticated. +func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { + var ( + username = req.FormValue("username") + password = req.FormValue("password") + ) + + // if the username or password is empty we re-direct to the login screen. + if len(username) == 0 || len(password) == 0 { + http.Redirect(res, req, "/login/form", http.StatusSeeOther) + return nil, nil + } + + client := c.newClient() + + // try to fetch drone token if it exists + var accessToken string + tokens, err := client.ListAccessTokens(username, password) + if err == nil { + for _, token := range tokens { + if token.Name == "drone" { + accessToken = token.Sha1 + break + } + } + } + + // if drone token not found, create it + if accessToken == "" { + token, terr := client.CreateAccessToken( + username, + password, + gitea.CreateAccessTokenOption{Name: "drone"}, + ) + if terr != nil { + return nil, terr + } + accessToken = token.Sha1 + } + + client = c.newClientToken(accessToken) + account, err := client.GetUserInfo(username) + if err != nil { + return nil, err + } + + return &model.User{ + Token: accessToken, + Login: account.UserName, + Email: account.Email, + Avatar: expandAvatar(c.URL, account.AvatarURL), + }, nil +} + +// Auth is not supported by the Gitea driver. +func (c *client) Auth(token, secret string) (string, error) { + return "", fmt.Errorf("Not Implemented") +} + +// Teams is supported by the Gitea driver. +func (c *client) Teams(u *model.User) ([]*model.Team, error) { + client := c.newClientToken(u.Token) + orgs, err := client.ListMyOrgs() + if err != nil { + return nil, err + } + + var teams []*model.Team + for _, org := range orgs { + teams = append(teams, toTeam(org, c.URL)) + } + return teams, nil +} + +// TeamPerm is not supported by the Gitea driver. +func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) { + return nil, nil +} + +// Repo returns the named Gitea repository. +func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { + client := c.newClientToken(u.Token) + repo, err := client.GetRepo(owner, name) + if err != nil { + return nil, err + } + if c.PrivateMode { + repo.Private = true + } + return toRepo(repo), nil +} + +// Repos returns a list of all repositories for the Gitea account, including +// organization repositories. +func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { + repos := []*model.RepoLite{} + + client := c.newClientToken(u.Token) + all, err := client.ListMyRepos() + if err != nil { + return repos, err + } + + for _, repo := range all { + repos = append(repos, toRepoLite(repo)) + } + return repos, err +} + +// Perm returns the user permissions for the named Gitea repository. +func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) { + client := c.newClientToken(u.Token) + repo, err := client.GetRepo(owner, name) + if err != nil { + return nil, err + } + return toPerm(repo.Permissions), nil +} + +// File fetches the file from the Gitea repository and returns its contents. +func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { + client := c.newClientToken(u.Token) + cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f) + return cfg, err +} + +// FileRef fetches the file from the Gitea repository and returns its contents. +func (c *client) FileRef(u *model.User, r *model.Repo, ref, f string) ([]byte, error) { + return c.newClientToken(u.Token).GetFile(r.Owner, r.Name, ref, f) +} + +// Status is supported by the Gitea driver. +func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { + client := c.newClientToken(u.Token) + + status := getStatus(b.Status) + desc := getDesc(b.Status) + + _, err := client.CreateStatus( + r.Owner, + r.Name, + b.Commit, + gitea.CreateStatusOption{ + State: status, + TargetURL: link, + Description: desc, + Context: "", + }, + ) + + return err +} + +// Netrc returns a netrc file capable of authenticating Gitea requests and +// cloning Gitea repositories. The netrc will use the global machine account +// when configured. +func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { + if c.Password != "" { + return &model.Netrc{ + Login: c.Username, + Password: c.Password, + Machine: c.Machine, + }, nil + } + return &model.Netrc{ + Login: u.Token, + Password: "x-oauth-basic", + Machine: c.Machine, + }, nil +} + +// Activate activates the repository by registering post-commit hooks with +// the Gitea repository. +func (c *client) Activate(u *model.User, r *model.Repo, link string) error { + config := map[string]string{ + "url": link, + "secret": r.Hash, + "content_type": "json", + } + hook := gitea.CreateHookOption{ + Type: "gitea", + Config: config, + Events: []string{"push", "create", "pull_request"}, + Active: true, + } + + client := c.newClientToken(u.Token) + _, err := client.CreateRepoHook(r.Owner, r.Name, hook) + return err +} + +// Deactivate deactives the repository be removing repository push hooks from +// the Gitea repository. +func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error { + client := c.newClientToken(u.Token) + + hooks, err := client.ListRepoHooks(r.Owner, r.Name) + if err != nil { + return err + } + + hook := matchingHooks(hooks, link) + if hook != nil { + return client.DeleteRepoHook(r.Owner, r.Name, hook.ID) + } + + return nil +} + +// Hook parses the incoming Gitea hook and returns the Repository and Build +// details. If the hook is unsupported nil values are returned. +func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) { + return parseHook(r) +} + +// helper function to return the Gitea client +func (c *client) newClient() *gitea.Client { + return c.newClientToken("") +} + +// helper function to return the Gitea client +func (c *client) newClientToken(token string) *gitea.Client { + client := gitea.NewClient(c.URL, token) + if c.SkipVerify { + httpClient := &http.Client{} + httpClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client.SetHTTPClient(httpClient) + } + return client +} + +// helper function to return matching hooks. +func matchingHooks(hooks []*gitea.Hook, rawurl string) *gitea.Hook { + link, err := url.Parse(rawurl) + if err != nil { + return nil + } + for _, hook := range hooks { + if val, ok := hook.Config["url"]; ok { + hookurl, err := url.Parse(val) + if err == nil && hookurl.Host == link.Host { + return hook + } + } + } + return nil +} diff --git a/remote/gitea/gitea_test.go b/remote/gitea/gitea_test.go new file mode 100644 index 000000000..15f00e0a7 --- /dev/null +++ b/remote/gitea/gitea_test.go @@ -0,0 +1,181 @@ +package gitea + +import ( + "net/http/httptest" + "testing" + + "github.com/drone/drone/model" + "github.com/drone/drone/remote/gitea/fixtures" + "github.com/franela/goblin" + "github.com/gin-gonic/gin" +) + +func Test_gitea(t *testing.T) { + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(fixtures.Handler()) + c, _ := New(Opts{ + URL: s.URL, + SkipVerify: true, + }) + + g := goblin.Goblin(t) + g.Describe("Gitea", func() { + + g.After(func() { + s.Close() + }) + + g.Describe("Creating a remote", func() { + g.It("Should return client with specified options", func() { + remote, _ := New(Opts{ + URL: "http://localhost:8080", + Username: "someuser", + Password: "password", + SkipVerify: true, + PrivateMode: true, + }) + g.Assert(remote.(*client).URL).Equal("http://localhost:8080") + g.Assert(remote.(*client).Machine).Equal("localhost") + g.Assert(remote.(*client).Username).Equal("someuser") + g.Assert(remote.(*client).Password).Equal("password") + g.Assert(remote.(*client).SkipVerify).Equal(true) + g.Assert(remote.(*client).PrivateMode).Equal(true) + }) + g.It("Should handle malformed url", func() { + _, err := New(Opts{URL: "%gh&%ij"}) + g.Assert(err != nil).IsTrue() + }) + }) + + g.Describe("Generating a netrc file", func() { + g.It("Should return a netrc with the user token", func() { + remote, _ := New(Opts{ + URL: "http://gitea.com", + }) + netrc, _ := remote.Netrc(fakeUser, nil) + g.Assert(netrc.Machine).Equal("gitea.com") + g.Assert(netrc.Login).Equal(fakeUser.Token) + g.Assert(netrc.Password).Equal("x-oauth-basic") + }) + g.It("Should return a netrc with the machine account", func() { + remote, _ := New(Opts{ + URL: "http://gitea.com", + Username: "someuser", + Password: "password", + }) + netrc, _ := remote.Netrc(nil, nil) + g.Assert(netrc.Machine).Equal("gitea.com") + g.Assert(netrc.Login).Equal("someuser") + g.Assert(netrc.Password).Equal("password") + }) + }) + + g.Describe("Requesting a repository", func() { + g.It("Should return the repository details", func() { + repo, err := c.Repo(fakeUser, fakeRepo.Owner, fakeRepo.Name) + g.Assert(err == nil).IsTrue() + g.Assert(repo.Owner).Equal(fakeRepo.Owner) + g.Assert(repo.Name).Equal(fakeRepo.Name) + g.Assert(repo.FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) + g.Assert(repo.IsPrivate).IsTrue() + g.Assert(repo.Clone).Equal("http://localhost/test_name/repo_name.git") + g.Assert(repo.Link).Equal("http://localhost/test_name/repo_name") + }) + g.It("Should handle a not found error", func() { + _, err := c.Repo(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + g.Assert(err != nil).IsTrue() + }) + }) + + g.Describe("Requesting repository permissions", func() { + g.It("Should return the permission details", func() { + perm, err := c.Perm(fakeUser, fakeRepo.Owner, fakeRepo.Name) + g.Assert(err == nil).IsTrue() + g.Assert(perm.Admin).IsTrue() + g.Assert(perm.Push).IsTrue() + g.Assert(perm.Pull).IsTrue() + }) + g.It("Should handle a not found error", func() { + _, err := c.Perm(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + g.Assert(err != nil).IsTrue() + }) + }) + + g.Describe("Requesting a repository list", func() { + g.It("Should return the repository list", func() { + repos, err := c.Repos(fakeUser) + g.Assert(err == nil).IsTrue() + g.Assert(repos[0].Owner).Equal(fakeRepo.Owner) + g.Assert(repos[0].Name).Equal(fakeRepo.Name) + g.Assert(repos[0].FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) + }) + g.It("Should handle a not found error", func() { + _, err := c.Repos(fakeUserNoRepos) + g.Assert(err != nil).IsTrue() + }) + }) + + g.It("Should register repository hooks", func() { + err := c.Activate(fakeUser, fakeRepo, "http://localhost") + g.Assert(err == nil).IsTrue() + }) + + g.It("Should remove repository hooks", func() { + err := c.Deactivate(fakeUser, fakeRepo, "http://localhost") + g.Assert(err == nil).IsTrue() + }) + + g.It("Should return a repository file", func() { + raw, err := c.File(fakeUser, fakeRepo, fakeBuild, ".drone.yml") + g.Assert(err == nil).IsTrue() + g.Assert(string(raw)).Equal("{ platform: linux/amd64 }") + }) + + g.It("Should return nil from send build status", func() { + err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://gitea.io") + g.Assert(err == nil).IsTrue() + }) + + g.Describe("Given an authentication request", func() { + g.It("Should redirect to login form") + g.It("Should create an access token") + g.It("Should handle an access token error") + g.It("Should return the authenticated user") + }) + + g.Describe("Given a repository hook", func() { + g.It("Should skip non-push events") + g.It("Should return push details") + g.It("Should handle a parsing error") + }) + }) +} + +var ( + fakeUser = &model.User{ + Login: "someuser", + Token: "cfcd2084", + } + + fakeUserNoRepos = &model.User{ + Login: "someuser", + Token: "repos_not_found", + } + + fakeRepo = &model.Repo{ + Owner: "test_name", + Name: "repo_name", + FullName: "test_name/repo_name", + } + + fakeRepoNotFound = &model.Repo{ + Owner: "test_name", + Name: "repo_not_found", + FullName: "test_name/repo_not_found", + } + + fakeBuild = &model.Build{ + Commit: "9ecad50", + } +) diff --git a/remote/gitea/helper.go b/remote/gitea/helper.go new file mode 100644 index 000000000..199e8ce2a --- /dev/null +++ b/remote/gitea/helper.go @@ -0,0 +1,221 @@ +package gitea + +import ( + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + "time" + + "code.gitea.io/sdk/gitea" + "github.com/drone/drone/model" +) + +// helper function that converts a Gitea repository to a Drone repository. +func toRepoLite(from *gitea.Repository) *model.RepoLite { + name := strings.Split(from.FullName, "/")[1] + avatar := expandAvatar( + from.HTMLURL, + from.Owner.AvatarURL, + ) + return &model.RepoLite{ + Name: name, + Owner: from.Owner.UserName, + FullName: from.FullName, + Avatar: avatar, + } +} + +// helper function that converts a Gitea repository to a Drone repository. +func toRepo(from *gitea.Repository) *model.Repo { + name := strings.Split(from.FullName, "/")[1] + avatar := expandAvatar( + from.HTMLURL, + from.Owner.AvatarURL, + ) + return &model.Repo{ + Kind: model.RepoGit, + Name: name, + Owner: from.Owner.UserName, + FullName: from.FullName, + Avatar: avatar, + Link: from.HTMLURL, + IsPrivate: from.Private, + Clone: from.CloneURL, + Branch: "master", + } +} + +// helper function that converts a Gitea permission to a Drone permission. +func toPerm(from *gitea.Permission) *model.Perm { + return &model.Perm{ + Pull: from.Pull, + Push: from.Push, + Admin: from.Admin, + } +} + +// helper function that converts a Gitea team to a Drone team. +func toTeam(from *gitea.Organization, link string) *model.Team { + return &model.Team{ + Login: from.UserName, + Avatar: expandAvatar(link, from.AvatarURL), + } +} + +// helper function that extracts the Build data from a Gitea push hook +func buildFromPush(hook *pushHook) *model.Build { + avatar := expandAvatar( + hook.Repo.URL, + fixMalformedAvatar(hook.Sender.Avatar), + ) + author := hook.Sender.Login + if author == "" { + author = hook.Sender.Username + } + sender := hook.Sender.Username + if sender == "" { + sender = hook.Sender.Login + } + + return &model.Build{ + Event: model.EventPush, + Commit: hook.After, + Ref: hook.Ref, + Link: hook.Compare, + Branch: strings.TrimPrefix(hook.Ref, "refs/heads/"), + Message: hook.Commits[0].Message, + Avatar: avatar, + Author: author, + Timestamp: time.Now().UTC().Unix(), + Sender: sender, + } +} + +// helper function that extracts the Build data from a Gitea tag hook +func buildFromTag(hook *pushHook) *model.Build { + avatar := expandAvatar( + hook.Repo.URL, + fixMalformedAvatar(hook.Sender.Avatar), + ) + author := hook.Sender.Login + if author == "" { + author = hook.Sender.Username + } + sender := hook.Sender.Username + if sender == "" { + sender = hook.Sender.Login + } + + return &model.Build{ + Event: model.EventTag, + Commit: hook.After, + Ref: fmt.Sprintf("refs/tags/%s", hook.Ref), + Link: fmt.Sprintf("%s/src/%s", hook.Repo.URL, hook.Ref), + Branch: fmt.Sprintf("refs/tags/%s", hook.Ref), + Message: fmt.Sprintf("created tag %s", hook.Ref), + Avatar: avatar, + Author: author, + Sender: sender, + Timestamp: time.Now().UTC().Unix(), + } +} + +// helper function that extracts the Build data from a Gitea pull_request hook +func buildFromPullRequest(hook *pullRequestHook) *model.Build { + avatar := expandAvatar( + hook.Repo.URL, + fixMalformedAvatar(hook.PullRequest.User.Avatar), + ) + sender := hook.Sender.Username + if sender == "" { + sender = hook.Sender.Login + } + build := &model.Build{ + Event: model.EventPull, + Commit: hook.PullRequest.Head.Sha, + Link: hook.PullRequest.URL, + Ref: fmt.Sprintf("refs/pull/%d/head", hook.Number), + Branch: hook.PullRequest.BaseBranch, + Message: hook.PullRequest.Title, + Author: hook.PullRequest.User.Username, + Avatar: avatar, + Sender: sender, + Title: hook.PullRequest.Title, + Refspec: fmt.Sprintf("%s:%s", + hook.PullRequest.HeadBranch, + hook.PullRequest.BaseBranch, + ), + } + return build +} + +// helper function that extracts the Repository data from a Gitea push hook +func repoFromPush(hook *pushHook) *model.Repo { + return &model.Repo{ + Name: hook.Repo.Name, + Owner: hook.Repo.Owner.Username, + FullName: hook.Repo.FullName, + Link: hook.Repo.URL, + } +} + +// helper function that extracts the Repository data from a Gitea pull_request hook +func repoFromPullRequest(hook *pullRequestHook) *model.Repo { + return &model.Repo{ + Name: hook.Repo.Name, + Owner: hook.Repo.Owner.Username, + FullName: hook.Repo.FullName, + Link: hook.Repo.URL, + } +} + +// helper function that parses a push hook from a read closer. +func parsePush(r io.Reader) (*pushHook, error) { + push := new(pushHook) + err := json.NewDecoder(r).Decode(push) + return push, err +} + +func parsePullRequest(r io.Reader) (*pullRequestHook, error) { + pr := new(pullRequestHook) + err := json.NewDecoder(r).Decode(pr) + return pr, err +} + +// fixMalformedAvatar is a helper function that fixes an avatar url if malformed +// (currently a known bug with gitea) +func fixMalformedAvatar(url string) string { + index := strings.Index(url, "///") + if index != -1 { + return url[index+1:] + } + index = strings.Index(url, "//avatars/") + if index != -1 { + return strings.Replace(url, "//avatars/", "/avatars/", -1) + } + return url +} + +// expandAvatar is a helper function that converts a relative avatar URL to the +// absolute url. +func expandAvatar(repo, rawurl string) string { + aurl, err := url.Parse(rawurl) + if err != nil { + return rawurl + } + if aurl.IsAbs() { + // Url is already absolute + return aurl.String() + } + + // Resolve to base + burl, err := url.Parse(repo) + if err != nil { + return rawurl + } + aurl = burl.ResolveReference(aurl) + + return aurl.String() +} diff --git a/remote/gitea/helper_test.go b/remote/gitea/helper_test.go new file mode 100644 index 000000000..1bacc15f3 --- /dev/null +++ b/remote/gitea/helper_test.go @@ -0,0 +1,249 @@ +package gitea + +import ( + "bytes" + "testing" + + "code.gitea.io/sdk/gitea" + "github.com/drone/drone/model" + "github.com/drone/drone/remote/gitea/fixtures" + "github.com/franela/goblin" +) + +func Test_parse(t *testing.T) { + + g := goblin.Goblin(t) + g.Describe("Gitea", func() { + + g.It("Should parse push hook payload", func() { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, err := parsePush(buf) + g.Assert(err == nil).IsTrue() + g.Assert(hook.Ref).Equal("refs/heads/master") + g.Assert(hook.After).Equal("ef98532add3b2feb7a137426bba1248724367df5") + g.Assert(hook.Before).Equal("4b2626259b5a97b6b4eab5e6cca66adb986b672b") + g.Assert(hook.Compare).Equal("http://gitea.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5") + g.Assert(hook.Repo.Name).Equal("hello-world") + g.Assert(hook.Repo.URL).Equal("http://gitea.golang.org/gordon/hello-world") + g.Assert(hook.Repo.Owner.Name).Equal("gordon") + g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") + g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") + g.Assert(hook.Repo.Owner.Username).Equal("gordon") + g.Assert(hook.Repo.Private).Equal(true) + g.Assert(hook.Pusher.Name).Equal("gordon") + g.Assert(hook.Pusher.Email).Equal("gordon@golang.org") + g.Assert(hook.Pusher.Username).Equal("gordon") + g.Assert(hook.Sender.Login).Equal("gordon") + g.Assert(hook.Sender.Avatar).Equal("http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") + }) + + g.It("Should parse tag hook payload", func() { + buf := bytes.NewBufferString(fixtures.HookPushTag) + hook, err := parsePush(buf) + g.Assert(err == nil).IsTrue() + g.Assert(hook.Ref).Equal("v1.0.0") + g.Assert(hook.Repo.Name).Equal("hello-world") + g.Assert(hook.Repo.URL).Equal("http://gitea.golang.org/gordon/hello-world") + g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") + g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") + g.Assert(hook.Repo.Owner.Username).Equal("gordon") + g.Assert(hook.Repo.Private).Equal(true) + g.Assert(hook.Sender.Username).Equal("gordon") + g.Assert(hook.Sender.Avatar).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") + }) + + g.It("Should parse pull_request hook payload", func() { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, err := parsePullRequest(buf) + g.Assert(err == nil).IsTrue() + g.Assert(hook.Action).Equal("opened") + g.Assert(hook.Number).Equal(int64(1)) + + g.Assert(hook.Repo.Name).Equal("hello-world") + g.Assert(hook.Repo.URL).Equal("http://gitea.golang.org/gordon/hello-world") + g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") + g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") + g.Assert(hook.Repo.Owner.Username).Equal("gordon") + g.Assert(hook.Repo.Private).Equal(true) + g.Assert(hook.Sender.Username).Equal("gordon") + g.Assert(hook.Sender.Avatar).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") + + g.Assert(hook.PullRequest.Title).Equal("Update the README with new information") + g.Assert(hook.PullRequest.Body).Equal("please merge") + g.Assert(hook.PullRequest.State).Equal("open") + g.Assert(hook.PullRequest.User.Username).Equal("gordon") + g.Assert(hook.PullRequest.Base.Label).Equal("master") + g.Assert(hook.PullRequest.Base.Ref).Equal("master") + g.Assert(hook.PullRequest.Head.Label).Equal("feature/changes") + g.Assert(hook.PullRequest.Head.Ref).Equal("feature/changes") + }) + + g.It("Should return a Build struct from a push hook", func() { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + build := buildFromPush(hook) + g.Assert(build.Event).Equal(model.EventPush) + g.Assert(build.Commit).Equal(hook.After) + g.Assert(build.Ref).Equal(hook.Ref) + g.Assert(build.Link).Equal(hook.Compare) + g.Assert(build.Branch).Equal("master") + g.Assert(build.Message).Equal(hook.Commits[0].Message) + g.Assert(build.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") + g.Assert(build.Author).Equal(hook.Sender.Login) + + }) + + g.It("Should return a Repo struct from a push hook", func() { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + repo := repoFromPush(hook) + g.Assert(repo.Name).Equal(hook.Repo.Name) + g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username) + g.Assert(repo.FullName).Equal("gordon/hello-world") + g.Assert(repo.Link).Equal(hook.Repo.URL) + }) + + g.It("Should return a Build struct from a pull_request hook", func() { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + build := buildFromPullRequest(hook) + g.Assert(build.Event).Equal(model.EventPull) + g.Assert(build.Commit).Equal(hook.PullRequest.Head.Sha) + g.Assert(build.Ref).Equal("refs/pull/1/head") + g.Assert(build.Link).Equal(hook.PullRequest.URL) + g.Assert(build.Branch).Equal("master") + g.Assert(build.Message).Equal(hook.PullRequest.Title) + g.Assert(build.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") + g.Assert(build.Author).Equal(hook.PullRequest.User.Username) + + }) + + g.It("Should return a Repo struct from a pull_request hook", func() { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + repo := repoFromPullRequest(hook) + g.Assert(repo.Name).Equal(hook.Repo.Name) + g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username) + g.Assert(repo.FullName).Equal("gordon/hello-world") + g.Assert(repo.Link).Equal(hook.Repo.URL) + }) + + g.It("Should return a Perm struct from a Gitea Perm", func() { + perms := []gitea.Permission{ + {true, true, true}, + {true, true, false}, + {true, false, false}, + } + for _, from := range perms { + perm := toPerm(&from) + g.Assert(perm.Pull).Equal(from.Pull) + g.Assert(perm.Push).Equal(from.Push) + g.Assert(perm.Admin).Equal(from.Admin) + } + }) + + g.It("Should return a Team struct from a Gitea Org", func() { + from := &gitea.Organization{ + UserName: "drone", + AvatarURL: "/avatars/1", + } + + to := toTeam(from, "http://localhost:80") + g.Assert(to.Login).Equal(from.UserName) + g.Assert(to.Avatar).Equal("http://localhost:80/avatars/1") + }) + + g.It("Should return a Repo struct from a Gitea Repo", func() { + from := gitea.Repository{ + FullName: "gophers/hello-world", + Owner: &gitea.User{ + UserName: "gordon", + AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + CloneURL: "http://gitea.golang.org/gophers/hello-world.git", + HTMLURL: "http://gitea.golang.org/gophers/hello-world", + Private: true, + } + repo := toRepo(&from) + g.Assert(repo.FullName).Equal(from.FullName) + g.Assert(repo.Owner).Equal(from.Owner.UserName) + g.Assert(repo.Name).Equal("hello-world") + g.Assert(repo.Branch).Equal("master") + g.Assert(repo.Link).Equal(from.HTMLURL) + g.Assert(repo.Clone).Equal(from.CloneURL) + g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL) + g.Assert(repo.IsPrivate).Equal(from.Private) + }) + + g.It("Should return a RepoLite struct from a Gitea Repo", func() { + from := gitea.Repository{ + FullName: "gophers/hello-world", + Owner: &gitea.User{ + UserName: "gordon", + AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + } + repo := toRepoLite(&from) + g.Assert(repo.FullName).Equal(from.FullName) + g.Assert(repo.Owner).Equal(from.Owner.UserName) + g.Assert(repo.Name).Equal("hello-world") + g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL) + }) + + g.It("Should correct a malformed avatar url", func() { + + var urls = []struct { + Before string + After string + }{ + { + "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "http://gitea.golang.org/avatars/1", + "http://gitea.golang.org/avatars/1", + }, + { + "http://gitea.golang.org//avatars/1", + "http://gitea.golang.org/avatars/1", + }, + } + + for _, url := range urls { + got := fixMalformedAvatar(url.Before) + g.Assert(got).Equal(url.After) + } + }) + + g.It("Should expand the avatar url", func() { + var urls = []struct { + Before string + After string + }{ + { + "/avatars/1", + "http://gitea.io/avatars/1", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "/gitea/avatars/2", + "http://gitea.io/gitea/avatars/2", + }, + } + + var repo = "http://gitea.io/foo/bar" + for _, url := range urls { + got := expandAvatar(repo, url.Before) + g.Assert(got).Equal(url.After) + } + }) + }) +} diff --git a/remote/gitea/parse.go b/remote/gitea/parse.go new file mode 100644 index 000000000..650fae237 --- /dev/null +++ b/remote/gitea/parse.go @@ -0,0 +1,107 @@ +package gitea + +import ( + "io" + "net/http" + + "github.com/drone/drone/model" +) + +const ( + hookEvent = "X-Gitea-Event" + hookPush = "push" + hookCreated = "create" + hookPullRequest = "pull_request" + + actionOpen = "opened" + actionSync = "synchronize" + + stateOpen = "open" + + refBranch = "branch" + refTag = "tag" +) + +// parseHook parses a Gitea hook from an http.Request request and returns +// Repo and Build detail. If a hook type is unsupported nil values are returned. +func parseHook(r *http.Request) (*model.Repo, *model.Build, error) { + switch r.Header.Get(hookEvent) { + case hookPush: + return parsePushHook(r.Body) + case hookCreated: + return parseCreatedHook(r.Body) + case hookPullRequest: + return parsePullRequestHook(r.Body) + } + return nil, nil, nil +} + +// parsePushHook parses a push hook and returns the Repo and Build details. +// If the commit type is unsupported nil values are returned. +func parsePushHook(payload io.Reader) (*model.Repo, *model.Build, error) { + var ( + repo *model.Repo + build *model.Build + ) + + push, err := parsePush(payload) + if err != nil { + return nil, nil, err + } + + // is this even needed? + if push.RefType == refBranch { + return nil, nil, nil + } + + repo = repoFromPush(push) + build = buildFromPush(push) + return repo, build, err +} + +// parseCreatedHook parses a push hook and returns the Repo and Build details. +// If the commit type is unsupported nil values are returned. +func parseCreatedHook(payload io.Reader) (*model.Repo, *model.Build, error) { + var ( + repo *model.Repo + build *model.Build + ) + + push, err := parsePush(payload) + if err != nil { + return nil, nil, err + } + + if push.RefType != refTag { + return nil, nil, nil + } + + repo = repoFromPush(push) + build = buildFromTag(push) + return repo, build, err +} + +// parsePullRequestHook parses a pull_request hook and returns the Repo and Build details. +func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Build, error) { + var ( + repo *model.Repo + build *model.Build + ) + + pr, err := parsePullRequest(payload) + if err != nil { + return nil, nil, err + } + + // Don't trigger builds for non-code changes, or if PR is not open + if pr.Action != actionOpen && pr.Action != actionSync { + return nil, nil, nil + } + if pr.PullRequest.State != stateOpen { + return nil, nil, nil + } + + repo = repoFromPullRequest(pr) + build = buildFromPullRequest(pr) + return repo, build, err +} diff --git a/remote/gitea/parse_test.go b/remote/gitea/parse_test.go new file mode 100644 index 000000000..3ac0cb79a --- /dev/null +++ b/remote/gitea/parse_test.go @@ -0,0 +1 @@ +package gitea diff --git a/remote/gitea/types.go b/remote/gitea/types.go new file mode 100644 index 000000000..f648f8de1 --- /dev/null +++ b/remote/gitea/types.go @@ -0,0 +1,127 @@ +package gitea + +type pushHook struct { + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + Compare string `json:"compare_url"` + RefType string `json:"ref_type"` + + Pusher struct { + Name string `json:"name"` + Email string `json:"email"` + Login string `json:"login"` + Username string `json:"username"` + } `json:"pusher"` + + Repo struct { + ID int64 `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + URL string `json:"html_url"` + Private bool `json:"private"` + Owner struct { + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + } `json:"owner"` + } `json:"repository"` + + Commits []struct { + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + } `json:"commits"` + + Sender struct { + ID int64 `json:"id"` + Login string `json:"login"` + Username string `json:"username"` + Avatar string `json:"avatar_url"` + } `json:"sender"` +} + +type pullRequestHook struct { + Action string `json:"action"` + Number int64 `json:"number"` + PullRequest struct { + ID int64 `json:"id"` + User struct { + ID int64 `json:"id"` + Username string `json:"username"` + Name string `json:"full_name"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + } `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []string `json:"labels"` + State string `json:"state"` + URL string `json:"html_url"` + Mergeable bool `json:"mergeable"` + Merged bool `json:"merged"` + MergeBase string `json:"merge_base"` + BaseBranch string `json:"base_branch"` + Base struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + Repo struct { + ID int64 `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + URL string `json:"html_url"` + Private bool `json:"private"` + Owner struct { + ID int64 `json:"id"` + Username string `json:"username"` + Name string `json:"full_name"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + } `json:"owner"` + } `json:"repo"` + } `json:"base"` + HeadBranch string `json:"head_branch"` + Head struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + Repo struct { + ID int64 `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + URL string `json:"html_url"` + Private bool `json:"private"` + Owner struct { + ID int64 `json:"id"` + Username string `json:"username"` + Name string `json:"full_name"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + } `json:"owner"` + } `json:"repo"` + } `json:"head"` + } `json:"pull_request"` + Repo struct { + ID int64 `json:"id"` + Name string `json:"name"` + FullName string `json:"full_name"` + URL string `json:"html_url"` + Private bool `json:"private"` + Owner struct { + ID int64 `json:"id"` + Username string `json:"username"` + Name string `json:"full_name"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + } `json:"owner"` + } `json:"repository"` + Sender struct { + ID int64 `json:"id"` + Login string `json:"login"` + Username string `json:"username"` + Name string `json:"full_name"` + Email string `json:"email"` + Avatar string `json:"avatar_url"` + } `json:"sender"` +} diff --git a/router/middleware/remote.go b/router/middleware/remote.go index da1c454cb..b145ce5fe 100644 --- a/router/middleware/remote.go +++ b/router/middleware/remote.go @@ -7,6 +7,7 @@ import ( "github.com/drone/drone/remote" "github.com/drone/drone/remote/bitbucket" "github.com/drone/drone/remote/bitbucketserver" + "github.com/drone/drone/remote/gitea" "github.com/drone/drone/remote/github" "github.com/drone/drone/remote/gitlab" "github.com/drone/drone/remote/gogs" @@ -39,6 +40,8 @@ func setupRemote(c *cli.Context) (remote.Remote, error) { return setupStash(c) case c.Bool("gogs"): return setupGogs(c) + case c.Bool("gitea"): + return setupGitea(c) default: return nil, fmt.Errorf("version control system not configured") } @@ -63,6 +66,17 @@ func setupGogs(c *cli.Context) (remote.Remote, error) { }) } +// helper function to setup the Gitea remote from the CLI arguments. +func setupGitea(c *cli.Context) (remote.Remote, error) { + return gitea.New(gitea.Opts{ + URL: c.String("gitea-server"), + Username: c.String("gitea-git-username"), + Password: c.String("gitea-git-password"), + PrivateMode: c.Bool("gitea-private-mode"), + SkipVerify: c.Bool("gitea-skip-verify"), + }) +} + // helper function to setup the Stash remote from the CLI arguments. func setupStash(c *cli.Context) (remote.Remote, error) { return bitbucketserver.New(bitbucketserver.Opts{ diff --git a/vendor/code.gitea.io/sdk/LICENSE b/vendor/code.gitea.io/sdk/LICENSE new file mode 100644 index 000000000..10aeba46b --- /dev/null +++ b/vendor/code.gitea.io/sdk/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 The Gitea Authors +Copyright (c) 2014 The Gogs Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/code.gitea.io/sdk/gitea/admin_org.go b/vendor/code.gitea.io/sdk/gitea/admin_org.go new file mode 100644 index 000000000..4071b6f18 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_org.go @@ -0,0 +1,22 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminCreateOrg create an organization +func (c *Client) AdminCreateOrg(user string, opt CreateOrgOption) (*Organization, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + org := new(Organization) + return org, c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/orgs", user), + jsonHeader, bytes.NewReader(body), org) +} diff --git a/vendor/code.gitea.io/sdk/gitea/admin_repo.go b/vendor/code.gitea.io/sdk/gitea/admin_repo.go new file mode 100644 index 000000000..cf565ffa3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_repo.go @@ -0,0 +1,22 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AdminCreateRepo create a repo +func (c *Client) AdminCreateRepo(user string, opt CreateRepoOption) (*Repository, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + repo := new(Repository) + return repo, c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/repos", user), + jsonHeader, bytes.NewReader(body), repo) +} diff --git a/vendor/code.gitea.io/sdk/gitea/admin_user.go b/vendor/code.gitea.io/sdk/gitea/admin_user.go new file mode 100644 index 000000000..6ccad7e1c --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/admin_user.go @@ -0,0 +1,74 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CreateUserOption create user options +type CreateUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + Username string `json:"username" binding:"Required;AlphaDashDot;MaxSize(35)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"Required;Email;MaxSize(254)"` + Password string `json:"password" binding:"MaxSize(255)"` + SendNotify bool `json:"send_notify"` +} + +// AdminCreateUser create a user +func (c *Client) AdminCreateUser(opt CreateUserOption) (*User, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + user := new(User) + return user, c.getParsedResponse("POST", "/admin/users", jsonHeader, bytes.NewReader(body), user) +} + +// EditUserOption edit user options +type EditUserOption struct { + SourceID int64 `json:"source_id"` + LoginName string `json:"login_name"` + FullName string `json:"full_name" binding:"MaxSize(100)"` + Email string `json:"email" binding:"Required;Email;MaxSize(254)"` + Password string `json:"password" binding:"MaxSize(255)"` + Website string `json:"website" binding:"MaxSize(50)"` + Location string `json:"location" binding:"MaxSize(50)"` + Active *bool `json:"active"` + Admin *bool `json:"admin"` + AllowGitHook *bool `json:"allow_git_hook"` + AllowImportLocal *bool `json:"allow_import_local"` + MaxRepoCreation *int `json:"max_repo_creation"` +} + +// AdminEditUser modify user informations +func (c *Client) AdminEditUser(user string, opt EditUserOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PATCH", fmt.Sprintf("/admin/users/%s", user), jsonHeader, bytes.NewReader(body)) + return err +} + +// AdminDeleteUser delete one user according name +func (c *Client) AdminDeleteUser(user string) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/admin/users/%s", user), nil, nil) + return err +} + +// AdminCreateUserPublicKey create one user with options +func (c *Client) AdminCreateUserPublicKey(user string, opt CreateKeyOption) (*PublicKey, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + key := new(PublicKey) + return key, c.getParsedResponse("POST", fmt.Sprintf("/admin/users/%s/keys", user), jsonHeader, bytes.NewReader(body), key) +} diff --git a/vendor/code.gitea.io/sdk/gitea/doc.go b/vendor/code.gitea.io/sdk/gitea/doc.go new file mode 100644 index 000000000..6bd327db4 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/doc.go @@ -0,0 +1,5 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea // import "code.gitea.io/sdk/gitea" diff --git a/vendor/code.gitea.io/sdk/gitea/fork.go b/vendor/code.gitea.io/sdk/gitea/fork.go new file mode 100644 index 000000000..e2158e1a1 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/fork.go @@ -0,0 +1,38 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListForks list a repository's forks +func (c *Client) ListForks(user, repo string) ([]*Repository, error) { + forks := make([]*Repository, 10) + err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/forks", user, repo), + nil, nil, &forks) + return forks, err +} + +// CreateForkOption options for creating a fork +type CreateForkOption struct { + Organization *string `json:"organization"` +} + +// CreateFork create a fork of a repository +func (c *Client) CreateFork(user, repo string, form CreateForkOption) (*Repository, error) { + body, err := json.Marshal(form) + if err != nil { + return nil, err + } + fork := new(Repository) + err = c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/forks", user, repo), + jsonHeader, bytes.NewReader(body), &fork) + return fork, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/gitea.go b/vendor/code.gitea.io/sdk/gitea/gitea.go new file mode 100644 index 000000000..20cb44fee --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/gitea.go @@ -0,0 +1,101 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "strings" +) + +// Version return the library version +func Version() string { + return "0.12.3" +} + +// Client represents a Gogs API client. +type Client struct { + url string + accessToken string + client *http.Client +} + +// NewClient initializes and returns a API client. +func NewClient(url, token string) *Client { + return &Client{ + url: strings.TrimSuffix(url, "/"), + accessToken: token, + client: &http.Client{}, + } +} + +// SetHTTPClient replaces default http.Client with user given one. +func (c *Client) SetHTTPClient(client *http.Client) { + c.client = client +} + +func (c *Client) doRequest(method, path string, header http.Header, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest(method, c.url+"/api/v1"+path, body) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "token "+c.accessToken) + for k, v := range header { + req.Header[k] = v + } + + return c.client.Do(req) +} + +func (c *Client) getResponse(method, path string, header http.Header, body io.Reader) ([]byte, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + switch resp.StatusCode { + case 403: + return nil, errors.New("403 Forbidden") + case 404: + return nil, errors.New("404 Not Found") + } + + if resp.StatusCode/100 != 2 { + errMap := make(map[string]interface{}) + if err = json.Unmarshal(data, &errMap); err != nil { + return nil, err + } + return nil, errors.New(errMap["message"].(string)) + } + + return data, nil +} + +func (c *Client) getParsedResponse(method, path string, header http.Header, body io.Reader, obj interface{}) error { + data, err := c.getResponse(method, path, header, body) + if err != nil { + return err + } + return json.Unmarshal(data, obj) +} + +func (c *Client) getStatusCode(method, path string, header http.Header, body io.Reader) (int, error) { + resp, err := c.doRequest(method, path, header, body) + if err != nil { + return -1, err + } + defer resp.Body.Close() + + return resp.StatusCode, nil +} diff --git a/vendor/code.gitea.io/sdk/gitea/hook.go b/vendor/code.gitea.io/sdk/gitea/hook.go new file mode 100644 index 000000000..f93a8ba46 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/hook.go @@ -0,0 +1,391 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "strings" + "time" +) + +var ( + // ErrInvalidReceiveHook FIXME + ErrInvalidReceiveHook = errors.New("Invalid JSON payload received over webhook") +) + +// Hook a hook is a web hook when one repository changed +type Hook struct { + ID int64 `json:"id"` + Type string `json:"type"` + URL string `json:"-"` + Config map[string]string `json:"config"` + Events []string `json:"events"` + Active bool `json:"active"` + Updated time.Time `json:"updated_at"` + Created time.Time `json:"created_at"` +} + +// ListOrgHooks list all the hooks of one organization +func (c *Client) ListOrgHooks(org string) ([]*Hook, error) { + hooks := make([]*Hook, 0, 10) + return hooks, c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks", org), nil, nil, &hooks) +} + +// ListRepoHooks list all the hooks of one repository +func (c *Client) ListRepoHooks(user, repo string) ([]*Hook, error) { + hooks := make([]*Hook, 0, 10) + return hooks, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), nil, nil, &hooks) +} + +// GetOrgHook get a hook of an organization +func (c *Client) GetOrgHook(org string, id int64) (*Hook, error) { + h := new(Hook) + return h, c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), nil, nil, h) +} + +// GetRepoHook get a hook of a repository +func (c *Client) GetRepoHook(user, repo string, id int64) (*Hook, error) { + h := new(Hook) + return h, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil, h) +} + +// CreateHookOption options when create a hook +type CreateHookOption struct { + Type string `json:"type" binding:"Required"` + Config map[string]string `json:"config" binding:"Required"` + Events []string `json:"events"` + Active bool `json:"active"` +} + +// CreateOrgHook create one hook for an organization, with options +func (c *Client) CreateOrgHook(org string, opt CreateHookOption) (*Hook, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + h := new(Hook) + return h, c.getParsedResponse("POST", fmt.Sprintf("/orgs/%s/hooks", org), jsonHeader, bytes.NewReader(body), h) +} + +// CreateRepoHook create one hook for a repository, with options +func (c *Client) CreateRepoHook(user, repo string, opt CreateHookOption) (*Hook, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + h := new(Hook) + return h, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/hooks", user, repo), jsonHeader, bytes.NewReader(body), h) +} + +// EditHookOption options when modify one hook +type EditHookOption struct { + Config map[string]string `json:"config"` + Events []string `json:"events"` + Active *bool `json:"active"` +} + +// EditOrgHook modify one hook of an organization, with hook id and options +func (c *Client) EditOrgHook(org string, id int64, opt EditHookOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PATCH", fmt.Sprintf("/orgs/%s/hooks/%d", org, id), jsonHeader, bytes.NewReader(body)) + return err +} + +// EditRepoHook modify one hook of a repository, with hook id and options +func (c *Client) EditRepoHook(user, repo string, id int64, opt EditHookOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PATCH", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), jsonHeader, bytes.NewReader(body)) + return err +} + +// DeleteOrgHook delete one hook from an organization, with hook id +func (c *Client) DeleteOrgHook(org string, id int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/org/%s/hooks/%d", org, id), nil, nil) + return err +} + +// DeleteRepoHook delete one hook from a repository, with hook id +func (c *Client) DeleteRepoHook(user, repo string, id int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/hooks/%d", user, repo, id), nil, nil) + return err +} + +// Payloader payload is some part of one hook +type Payloader interface { + SetSecret(string) + JSONPayload() ([]byte, error) +} + +// PayloadUser FIXME +type PayloadUser struct { + Name string `json:"name"` + Email string `json:"email"` + UserName string `json:"username"` +} + +// PayloadCommit FIXME: consider use same format as API when commits API are added. +type PayloadCommit struct { + ID string `json:"id"` + Message string `json:"message"` + URL string `json:"url"` + Author *PayloadUser `json:"author"` + Committer *PayloadUser `json:"committer"` + Verification *PayloadCommitVerification `json:"verification"` + Timestamp time.Time `json:"timestamp"` +} + +// PayloadCommitVerification represent the GPG verification part of a commit. FIXME: like PayloadCommit consider use same format as API when commits API are added. +type PayloadCommitVerification struct { + Verified bool `json:"verified"` + Reason string `json:"reason"` + Signature string `json:"signature"` + Payload string `json:"payload"` +} + +var ( + _ Payloader = &CreatePayload{} + _ Payloader = &PushPayload{} + _ Payloader = &IssuePayload{} + _ Payloader = &PullRequestPayload{} +) + +// _________ __ +// \_ ___ \_______ ____ _____ _/ |_ ____ +// / \ \/\_ __ \_/ __ \\__ \\ __\/ __ \ +// \ \____| | \/\ ___/ / __ \| | \ ___/ +// \______ /|__| \___ >____ /__| \___ > +// \/ \/ \/ \/ + +// CreatePayload FIXME +type CreatePayload struct { + Secret string `json:"secret"` + Sha string `json:"sha"` + Ref string `json:"ref"` + RefType string `json:"ref_type"` + Repo *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// SetSecret FIXME +func (p *CreatePayload) SetSecret(secret string) { + p.Secret = secret +} + +// JSONPayload return payload information +func (p *CreatePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ParseCreateHook parses create event hook content. +func ParseCreateHook(raw []byte) (*CreatePayload, error) { + hook := new(CreatePayload) + if err := json.Unmarshal(raw, hook); err != nil { + return nil, err + } + + // it is possible the JSON was parsed, however, + // was not from Gogs (maybe was from Bitbucket) + // So we'll check to be sure certain key fields + // were populated + switch { + case hook.Repo == nil: + return nil, ErrInvalidReceiveHook + case len(hook.Ref) == 0: + return nil, ErrInvalidReceiveHook + } + return hook, nil +} + +// __________ .__ +// \______ \__ __ _____| |__ +// | ___/ | \/ ___/ | \ +// | | | | /\___ \| Y \ +// |____| |____//____ >___| / +// \/ \/ + +// PushPayload represents a payload information of push event. +type PushPayload struct { + Secret string `json:"secret"` + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + CompareURL string `json:"compare_url"` + Commits []*PayloadCommit `json:"commits"` + Repo *Repository `json:"repository"` + Pusher *User `json:"pusher"` + Sender *User `json:"sender"` +} + +// SetSecret FIXME +func (p *PushPayload) SetSecret(secret string) { + p.Secret = secret +} + +// JSONPayload FIXME +func (p *PushPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ParsePushHook parses push event hook content. +func ParsePushHook(raw []byte) (*PushPayload, error) { + hook := new(PushPayload) + if err := json.Unmarshal(raw, hook); err != nil { + return nil, err + } + + switch { + case hook.Repo == nil: + return nil, ErrInvalidReceiveHook + case len(hook.Ref) == 0: + return nil, ErrInvalidReceiveHook + } + return hook, nil +} + +// Branch returns branch name from a payload +func (p *PushPayload) Branch() string { + return strings.Replace(p.Ref, "refs/heads/", "", -1) +} + +// .___ +// | | ______ ________ __ ____ +// | |/ ___// ___/ | \_/ __ \ +// | |\___ \ \___ \| | /\ ___/ +// |___/____ >____ >____/ \___ > +// \/ \/ \/ + +// HookIssueAction FIXME +type HookIssueAction string + +const ( + // HookIssueOpened opened + HookIssueOpened HookIssueAction = "opened" + // HookIssueClosed closed + HookIssueClosed HookIssueAction = "closed" + // HookIssueReOpened reopened + HookIssueReOpened HookIssueAction = "reopened" + // HookIssueEdited edited + HookIssueEdited HookIssueAction = "edited" + // HookIssueAssigned assigned + HookIssueAssigned HookIssueAction = "assigned" + // HookIssueUnassigned unassigned + HookIssueUnassigned HookIssueAction = "unassigned" + // HookIssueLabelUpdated label_updated + HookIssueLabelUpdated HookIssueAction = "label_updated" + // HookIssueLabelCleared label_cleared + HookIssueLabelCleared HookIssueAction = "label_cleared" + // HookIssueSynchronized synchronized + HookIssueSynchronized HookIssueAction = "synchronized" + // HookIssueMilestoned is an issue action for when a milestone is set on an issue. + HookIssueMilestoned HookIssueAction = "milestoned" + // HookIssueDemilestoned is an issue action for when a milestone is cleared on an issue. + HookIssueDemilestoned HookIssueAction = "demilestoned" +) + +// IssuePayload represents the payload information that is sent along with an issue event. +type IssuePayload struct { + Secret string `json:"secret"` + Action HookIssueAction `json:"action"` + Index int64 `json:"number"` + Changes *ChangesPayload `json:"changes,omitempty"` + Issue *Issue `json:"issue"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// SetSecret modifies the secret of the IssuePayload. +func (p *IssuePayload) SetSecret(secret string) { + p.Secret = secret +} + +// JSONPayload encodes the IssuePayload to JSON, with an indentation of two spaces. +func (p *IssuePayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +// ChangesFromPayload FIXME +type ChangesFromPayload struct { + From string `json:"from"` +} + +// ChangesPayload FIXME +type ChangesPayload struct { + Title *ChangesFromPayload `json:"title,omitempty"` + Body *ChangesFromPayload `json:"body,omitempty"` +} + +// __________ .__ .__ __________ __ +// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_ +// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\ +// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | | +// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__| +// \/ \/ |__| \/ \/ + +// PullRequestPayload represents a payload information of pull request event. +type PullRequestPayload struct { + Secret string `json:"secret"` + Action HookIssueAction `json:"action"` + Index int64 `json:"number"` + Changes *ChangesPayload `json:"changes,omitempty"` + PullRequest *PullRequest `json:"pull_request"` + Repository *Repository `json:"repository"` + Sender *User `json:"sender"` +} + +// SetSecret modifies the secret of the PullRequestPayload. +func (p *PullRequestPayload) SetSecret(secret string) { + p.Secret = secret +} + +// JSONPayload FIXME +func (p *PullRequestPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} + +//__________ .__ __ +//\______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. +// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | +// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ | +// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____| +// \/ \/|__| \/ \/ + +// HookRepoAction an action that happens to a repo +type HookRepoAction string + +const ( + // HookRepoCreated created + HookRepoCreated HookRepoAction = "created" + // HookRepoDeleted deleted + HookRepoDeleted HookRepoAction = "deleted" +) + +// RepositoryPayload payload for repository webhooks +type RepositoryPayload struct { + Secret string `json:"secret"` + Action HookRepoAction `json:"action"` + Repository *Repository `json:"repository"` + Organization *User `json:"organization"` + Sender *User `json:"sender"` +} + +// SetSecret set the payload's secret +func (p *RepositoryPayload) SetSecret(secret string) { + p.Secret = secret +} + +// JSONPayload JSON representation of the payload +func (p *RepositoryPayload) JSONPayload() ([]byte, error) { + return json.MarshalIndent(p, "", " ") +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue.go b/vendor/code.gitea.io/sdk/gitea/issue.go new file mode 100644 index 000000000..729e54fe8 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue.go @@ -0,0 +1,118 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// StateType issue state type +type StateType string + +const ( + // StateOpen pr is opend + StateOpen StateType = "open" + // StateClosed pr is closed + StateClosed StateType = "closed" +) + +// PullRequestMeta PR info if an issue is a PR +type PullRequestMeta struct { + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` +} + +// Issue an issue to a repository +type Issue struct { + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + State StateType `json:"state"` + Comments int `json:"comments"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + + PullRequest *PullRequestMeta `json:"pull_request"` +} + +// ListIssueOption list issue options +type ListIssueOption struct { + Page int + State string +} + +// ListIssues returns all issues assigned the authenticated user +func (c *Client) ListIssues(opt ListIssueOption) ([]*Issue, error) { + issues := make([]*Issue, 0, 10) + return issues, c.getParsedResponse("GET", fmt.Sprintf("/issues?page=%d", opt.Page), nil, nil, &issues) +} + +// ListUserIssues returns all issues assigned to the authenticated user +func (c *Client) ListUserIssues(opt ListIssueOption) ([]*Issue, error) { + issues := make([]*Issue, 0, 10) + return issues, c.getParsedResponse("GET", fmt.Sprintf("/user/issues?page=%d", opt.Page), nil, nil, &issues) +} + +// ListRepoIssues returns all issues for a given repository +func (c *Client) ListRepoIssues(owner, repo string, opt ListIssueOption) ([]*Issue, error) { + issues := make([]*Issue, 0, 10) + return issues, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues?page=%d", owner, repo, opt.Page), nil, nil, &issues) +} + +// GetIssue returns a single issue for a given repository +func (c *Client) GetIssue(owner, repo string, index int64) (*Issue, error) { + issue := new(Issue) + return issue, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), nil, nil, issue) +} + +// CreateIssueOption options to create one issue +type CreateIssueOption struct { + Title string `json:"title" binding:"Required"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + Closed bool `json:"closed"` +} + +// CreateIssue create a new issue for a given repository +func (c *Client) CreateIssue(owner, repo string, opt CreateIssueOption) (*Issue, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + issue := new(Issue) + return issue, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues", owner, repo), + jsonHeader, bytes.NewReader(body), issue) +} + +// EditIssueOption edit issue options +type EditIssueOption struct { + Title string `json:"title"` + Body *string `json:"body"` + Assignee *string `json:"assignee"` + Milestone *int64 `json:"milestone"` + State *string `json:"state"` +} + +// EditIssue modify an existing issue for a given repository +func (c *Client) EditIssue(owner, repo string, index int64, opt EditIssueOption) (*Issue, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + issue := new(Issue) + return issue, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), issue) +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_comment.go b/vendor/code.gitea.io/sdk/gitea/issue_comment.go new file mode 100644 index 000000000..0977f98a4 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_comment.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Comment represents a comment in commit and issue page. +type Comment struct { + ID int64 `json:"id"` + HTMLURL string `json:"html_url"` + PRURL string `json:"pull_request_url"` + IssueURL string `json:"issue_url"` + Poster *User `json:"user"` + Body string `json:"body"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// ListIssueComments list comments on an issue. +func (c *Client) ListIssueComments(owner, repo string, index int64) ([]*Comment, error) { + comments := make([]*Comment, 0, 10) + return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), nil, nil, &comments) +} + +// ListRepoIssueComments list comments for a given repo. +func (c *Client) ListRepoIssueComments(owner, repo string) ([]*Comment, error) { + comments := make([]*Comment, 0, 10) + return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo), nil, nil, &comments) +} + +// CreateIssueCommentOption is option when creating an issue comment. +type CreateIssueCommentOption struct { + Body string `json:"body" binding:"Required"` +} + +// CreateIssueComment create comment on an issue. +func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + comment := new(Comment) + return comment, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment) +} + +// EditIssueCommentOption is option when editing an issue comment. +type EditIssueCommentOption struct { + Body string `json:"body" binding:"Required"` +} + +// EditIssueComment edits an issue comment. +func (c *Client) EditIssueComment(owner, repo string, index, commentID int64, opt EditIssueCommentOption) (*Comment, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + comment := new(Comment) + return comment, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/:%s/:%s/issues/%d/comments/%d", owner, repo, index, commentID), jsonHeader, bytes.NewReader(body), comment) +} + +// DeleteIssueComment deletes an issue comment. +func (c *Client) DeleteIssueComment(owner, repo string, index, commentID int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/comments/%d", owner, repo, index, commentID), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_label.go b/vendor/code.gitea.io/sdk/gitea/issue_label.go new file mode 100644 index 000000000..20607f2ef --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_label.go @@ -0,0 +1,116 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Label a label to an issue or a pr +type Label struct { + ID int64 `json:"id"` + Name string `json:"name"` + Color string `json:"color"` + URL string `json:"url"` +} + +// ListRepoLabels list lables of one reppsitory +func (c *Client) ListRepoLabels(owner, repo string) ([]*Label, error) { + labels := make([]*Label, 0, 10) + return labels, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels", owner, repo), nil, nil, &labels) +} + +// GetRepoLabel get one label of repository by repo it +// TODO: maybe we need get a label by name +func (c *Client) GetRepoLabel(owner, repo string, id int64) (*Label, error) { + label := new(Label) + return label, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil, label) +} + +// CreateLabelOption create options when one label of repository +type CreateLabelOption struct { + Name string `json:"name" binding:"Required"` + Color string `json:"color" binding:"Required;Size(7)"` +} + +// CreateLabel create one label of repository +func (c *Client) CreateLabel(owner, repo string, opt CreateLabelOption) (*Label, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + label := new(Label) + return label, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/labels", owner, repo), + jsonHeader, bytes.NewReader(body), label) +} + +// EditLabelOption edit label options +type EditLabelOption struct { + Name *string `json:"name"` + Color *string `json:"color"` +} + +// EditLabel modify one label with options +func (c *Client) EditLabel(owner, repo string, id int64, opt EditLabelOption) (*Label, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + label := new(Label) + return label, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), label) +} + +// DeleteLabel delete one label of repository by id +// TODO: maybe we need delete by name +func (c *Client) DeleteLabel(owner, repo string, id int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/labels/%d", owner, repo, id), nil, nil) + return err +} + +// IssueLabelsOption list one issue's labels options +type IssueLabelsOption struct { + Labels []int64 `json:"labels"` +} + +// GetIssueLabels get labels of one issue via issue id +func (c *Client) GetIssueLabels(owner, repo string, index int64) ([]*Label, error) { + labels := make([]*Label, 0, 5) + return labels, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil, &labels) +} + +// AddIssueLabels add one or more labels to one issue +func (c *Client) AddIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + var labels []*Label + return labels, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) +} + +// ReplaceIssueLabels replace old labels of issue with new labels +func (c *Client) ReplaceIssueLabels(owner, repo string, index int64, opt IssueLabelsOption) ([]*Label, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + var labels []*Label + return labels, c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), jsonHeader, bytes.NewReader(body), &labels) +} + +// DeleteIssueLabel delete one label of one issue by issue id and label id +// TODO: maybe we need delete by label name and issue id +func (c *Client) DeleteIssueLabel(owner, repo string, index, label int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, label), nil, nil) + return err +} + +// ClearIssueLabels delete all the labels of one issue. +func (c *Client) ClearIssueLabels(owner, repo string, index int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/labels", owner, repo, index), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/issue_milestone.go b/vendor/code.gitea.io/sdk/gitea/issue_milestone.go new file mode 100644 index 000000000..e35325e8d --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/issue_milestone.go @@ -0,0 +1,77 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Milestone milestone is a collection of issues on one repository +type Milestone struct { + ID int64 `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + State StateType `json:"state"` + OpenIssues int `json:"open_issues"` + ClosedIssues int `json:"closed_issues"` + Closed *time.Time `json:"closed_at"` + Deadline *time.Time `json:"due_on"` +} + +// ListRepoMilestones list all the milestones of one repository +func (c *Client) ListRepoMilestones(owner, repo string) ([]*Milestone, error) { + milestones := make([]*Milestone, 0, 10) + return milestones, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), nil, nil, &milestones) +} + +// GetMilestone get one milestone by repo name and milestone id +func (c *Client) GetMilestone(owner, repo string, id int64) (*Milestone, error) { + milestone := new(Milestone) + return milestone, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil, milestone) +} + +// CreateMilestoneOption options when creating milestone +type CreateMilestoneOption struct { + Title string `json:"title"` + Description string `json:"description"` + Deadline *time.Time `json:"due_on"` +} + +// CreateMilestone create one milestone with options +func (c *Client) CreateMilestone(owner, repo string, opt CreateMilestoneOption) (*Milestone, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + milestone := new(Milestone) + return milestone, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/milestones", owner, repo), jsonHeader, bytes.NewReader(body), milestone) +} + +// EditMilestoneOption options when modify milestone +type EditMilestoneOption struct { + Title string `json:"title"` + Description *string `json:"description"` + State *string `json:"state"` + Deadline *time.Time `json:"due_on"` +} + +// EditMilestone modify milestone with options +func (c *Client) EditMilestone(owner, repo string, id int64, opt EditMilestoneOption) (*Milestone, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + milestone := new(Milestone) + return milestone, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), jsonHeader, bytes.NewReader(body), milestone) +} + +// DeleteMilestone delete one milestone by milestone id +func (c *Client) DeleteMilestone(owner, repo string, id int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/milestones/%d", owner, repo, id), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/miscellaneous.go b/vendor/code.gitea.io/sdk/gitea/miscellaneous.go new file mode 100644 index 000000000..be1aa5e8b --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/miscellaneous.go @@ -0,0 +1,56 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// SearchResults results of search +// swagger:response SearchResults +type SearchResults struct { + OK bool `json:"ok"` + Data []*Repository `json:"data"` +} + +// SearchError error of failing search +// swagger:response SearchError +type SearchError struct { + OK bool `json:"ok"` + Error string `json:"error"` +} + +// MarkdownOption markdown options +// swagger:parameters renderMarkdown +type MarkdownOption struct { + // Text markdown to render + // + // in: body + Text string + // Mode to render + // + // in: body + Mode string + // Context to render + // + // in: body + Context string + // Is it a wiki page ? + // + // in: body + Wiki bool +} + +// MarkdownRender is a rendered markdown document +// swagger:response MarkdownRender +type MarkdownRender string + +// ServerVersion wraps the version of the server +// swagger:response ServerVersion +type ServerVersion struct { + Version string +} + +// ServerVersion returns the version of the server +func (c *Client) ServerVersion() (string, error) { + v := ServerVersion{} + return v.Version, c.getParsedResponse("GET", "/api/v1/version", nil, nil, &v) +} diff --git a/vendor/code.gitea.io/sdk/gitea/org.go b/vendor/code.gitea.io/sdk/gitea/org.go new file mode 100644 index 000000000..103674a70 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org.go @@ -0,0 +1,67 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// Organization a group of some repositories, users and teams +type Organization struct { + ID int64 `json:"id"` + UserName string `json:"username"` + FullName string `json:"full_name"` + AvatarURL string `json:"avatar_url"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` +} + +// ListMyOrgs list all of current user's organizations +func (c *Client) ListMyOrgs() ([]*Organization, error) { + orgs := make([]*Organization, 0, 5) + return orgs, c.getParsedResponse("GET", "/user/orgs", nil, nil, &orgs) +} + +// ListUserOrgs list all of some user's organizations +func (c *Client) ListUserOrgs(user string) ([]*Organization, error) { + orgs := make([]*Organization, 0, 5) + return orgs, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/orgs", user), nil, nil, &orgs) +} + +// GetOrg get one organization by name +func (c *Client) GetOrg(orgname string) (*Organization, error) { + org := new(Organization) + return org, c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s", orgname), nil, nil, org) +} + +// CreateOrgOption create one organization options +type CreateOrgOption struct { + UserName string `json:"username" binding:"Required"` + FullName string `json:"full_name"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` +} + +// EditOrgOption edit one organization options +type EditOrgOption struct { + FullName string `json:"full_name"` + Description string `json:"description"` + Website string `json:"website"` + Location string `json:"location"` +} + +// EditOrg modify one organization via options +func (c *Client) EditOrg(orgname string, opt EditOrgOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PATCH", fmt.Sprintf("/orgs/%s", orgname), jsonHeader, bytes.NewReader(body)) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/org_member.go b/vendor/code.gitea.io/sdk/gitea/org_member.go new file mode 100644 index 000000000..9bb95af3e --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org_member.go @@ -0,0 +1,26 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// AddOrgMembershipOption add user to organization options +type AddOrgMembershipOption struct { + Role string `json:"role" binding:"Required"` +} + +// AddOrgMembership add some one to an organization's member +func (c *Client) AddOrgMembership(org, user string, opt AddOrgMembershipOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PUT", fmt.Sprintf("/orgs/%s/membership/%s", org, user), jsonHeader, bytes.NewReader(body)) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/org_team.go b/vendor/code.gitea.io/sdk/gitea/org_team.go new file mode 100644 index 000000000..eddaa2d60 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/org_team.go @@ -0,0 +1,27 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +// Team is a sub virtual organization of one Organization +type Team struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Permission string `json:"permission"` +} + +// CreateTeamOption options when create team +type CreateTeamOption struct { + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"` + Description string `json:"description" binding:"MaxSize(255)"` + Permission string `json:"permission"` +} + +// EditTeamOption options when edit team +type EditTeamOption struct { + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"` + Description string `json:"description" binding:"MaxSize(255)"` + Permission string `json:"permission"` +} diff --git a/vendor/code.gitea.io/sdk/gitea/pull.go b/vendor/code.gitea.io/sdk/gitea/pull.go new file mode 100644 index 000000000..a50db96e3 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/pull.go @@ -0,0 +1,136 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// PullRequest represents a pull request API object. +type PullRequest struct { + ID int64 `json:"id"` + URL string `json:"url"` + Index int64 `json:"number"` + Poster *User `json:"user"` + Title string `json:"title"` + Body string `json:"body"` + Labels []*Label `json:"labels"` + Milestone *Milestone `json:"milestone"` + Assignee *User `json:"assignee"` + State StateType `json:"state"` + Comments int `json:"comments"` + + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + + Mergeable bool `json:"mergeable"` + HasMerged bool `json:"merged"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + + Base *PRBranchInfo `json:"base"` + Head *PRBranchInfo `json:"head"` + MergeBase string `json:"merge_base"` + + Created *time.Time `json:"created_at"` + Updated *time.Time `json:"updated_at"` +} + +// PRBranchInfo base branch info when send a PR +type PRBranchInfo struct { + Name string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + RepoID int64 `json:"repo_id"` + Repository *Repository `json:"repo"` +} + +// ListPullRequestsOptions options when list PRs +type ListPullRequestsOptions struct { + Page int `json:"page"` + State string `json:"state"` +} + +// ListRepoPullRequests list PRs of one repository +func (c *Client) ListRepoPullRequests(owner, repo string, opt ListPullRequestsOptions) ([]*PullRequest, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + prs := make([]*PullRequest, 0, 10) + return prs, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), jsonHeader, bytes.NewReader(body), &prs) +} + +// GetPullRequest get information of one PR +func (c *Client) GetPullRequest(owner, repo string, index int64) (*PullRequest, error) { + pr := new(PullRequest) + return pr, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, index), nil, nil, pr) +} + +// CreatePullRequestOption options when creating a pull request +type CreatePullRequestOption struct { + Head string `json:"head" binding:"Required"` + Base string `json:"base" binding:"Required"` + Title string `json:"title" binding:"Required"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` +} + +// CreatePullRequest create pull request with options +func (c *Client) CreatePullRequest(owner, repo string, opt CreatePullRequestOption) (*PullRequest, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + pr := new(PullRequest) + return pr, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/pulls", owner, repo), + jsonHeader, bytes.NewReader(body), pr) +} + +// EditPullRequestOption options when modify pull request +type EditPullRequestOption struct { + Title string `json:"title"` + Body string `json:"body"` + Assignee string `json:"assignee"` + Milestone int64 `json:"milestone"` + Labels []int64 `json:"labels"` + State *string `json:"state"` +} + +// EditPullRequest modify pull request with PR id and options +func (c *Client) EditPullRequest(owner, repo string, index int64, opt EditPullRequestOption) (*PullRequest, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + pr := new(PullRequest) + return pr, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/%s/%s/issues/%d", owner, repo, index), + jsonHeader, bytes.NewReader(body), pr) +} + +// MergePullRequest merge a PR to repository by PR id +func (c *Client) MergePullRequest(owner, repo string, index int64) error { + _, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) + return err +} + +// IsPullRequestMerged test if one PR is merged to one repository +func (c *Client) IsPullRequestMerged(owner, repo string, index int64) (bool, error) { + statusCode, err := c.getStatusCode("GET", fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, index), nil, nil) + + if err != nil { + return false, err + } + + return statusCode == 204, nil + +} diff --git a/vendor/code.gitea.io/sdk/gitea/release.go b/vendor/code.gitea.io/sdk/gitea/release.go new file mode 100644 index 000000000..cc841466d --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/release.go @@ -0,0 +1,101 @@ +// Copyright 2016 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Release represents a repository release +type Release struct { + ID int64 `json:"id"` + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + URL string `json:"url"` + TarURL string `json:"tarball_url"` + ZipURL string `json:"zipball_url"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` + CreatedAt time.Time `json:"created_at"` + PublishedAt time.Time `json:"published_at"` + Publisher *User `json:"author"` +} + +// ListReleases list releases of a repository +func (c *Client) ListReleases(user, repo string) ([]*Release, error) { + releases := make([]*Release, 0, 10) + err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases", user, repo), + nil, nil, &releases) + return releases, err +} + +// GetRelease get a release of a repository +func (c *Client) GetRelease(user, repo string, id int64) (*Release, error) { + r := new(Release) + err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), + nil, nil, &r) + return r, err +} + +// CreateReleaseOption options when creating a release +type CreateReleaseOption struct { + TagName string `json:"tag_name" binding:"Required"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft bool `json:"draft"` + IsPrerelease bool `json:"prerelease"` +} + +// CreateRelease create a release +func (c *Client) CreateRelease(user, repo string, form CreateReleaseOption) (*Release, error) { + body, err := json.Marshal(form) + if err != nil { + return nil, err + } + r := new(Release) + err = c.getParsedResponse("POST", + fmt.Sprintf("/repos/%s/%s/releases", user, repo), + jsonHeader, bytes.NewReader(body), r) + return r, err +} + +// EditReleaseOption options when editing a release +type EditReleaseOption struct { + TagName string `json:"tag_name"` + Target string `json:"target_commitish"` + Title string `json:"name"` + Note string `json:"body"` + IsDraft *bool `json:"draft"` + IsPrerelease *bool `json:"prerelease"` +} + +// EditRelease edit a release +func (c *Client) EditRelease(user, repo string, id int64, form EditReleaseOption) (*Release, error) { + body, err := json.Marshal(form) + if err != nil { + return nil, err + } + r := new(Release) + err = c.getParsedResponse("PATCH", + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), + jsonHeader, bytes.NewReader(body), r) + return r, err +} + +// DeleteRelease delete a release from a repository +func (c *Client) DeleteRelease(user, repo string, id int64) error { + _, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/releases/%d", user, repo, id), + nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo.go b/vendor/code.gitea.io/sdk/gitea/repo.go new file mode 100644 index 000000000..7e997cd5d --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo.go @@ -0,0 +1,167 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// Permission represents a API permission. +type Permission struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` +} + +// Repository represents a API repository. +// swagger:response Repository +type Repository struct { + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + FullName string `json:"full_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Parent *Repository `json:"parent"` + Mirror bool `json:"mirror"` + Size int `json:"size"` + HTMLURL string `json:"html_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + Website string `json:"website"` + Stars int `json:"stars_count"` + Forks int `json:"forks_count"` + Watchers int `json:"watchers_count"` + OpenIssues int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` + Permissions *Permission `json:"permissions,omitempty"` +} + +// RepositoryList represents a list of API repository. +// swagger:response RepositoryList +type RepositoryList []*Repository + +// ListMyRepos lists all repositories for the authenticated user that has access to. +func (c *Client) ListMyRepos() ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, c.getParsedResponse("GET", "/user/repos", nil, nil, &repos) +} + +// ListUserRepos list all repositories of one user by user's name +func (c *Client) ListUserRepos(user string) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/repos", user), nil, nil, &repos) +} + +// ListOrgRepos list all repositories of one organization by organization's name +func (c *Client) ListOrgRepos(org string) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, c.getParsedResponse("GET", fmt.Sprintf("/orgs/%s/repos", org), nil, nil, &repos) +} + +// CreateRepoOption options when creating repository +//swagger:parameters createOrgRepo +type CreateRepoOption struct { + // Name of the repository to create + // + // in: body + // unique: true + Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` + // Description of the repository to create + // + // in: body + Description string `json:"description" binding:"MaxSize(255)"` + // Is the repository to create private ? + // + // in: body + Private bool `json:"private"` + // Init the repository to create ? + // + // in: body + AutoInit bool `json:"auto_init"` + // Gitignores to use + // + // in: body + Gitignores string `json:"gitignores"` + // License to use + // + // in: body + License string `json:"license"` + // Readme of the repository to create + // + // in: body + Readme string `json:"readme"` +} + +// CreateRepo creates a repository for authenticated user. +func (c *Client) CreateRepo(opt CreateRepoOption) (*Repository, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + repo := new(Repository) + return repo, c.getParsedResponse("POST", "/user/repos", jsonHeader, bytes.NewReader(body), repo) +} + +// CreateOrgRepo creates an organization repository for authenticated user. +func (c *Client) CreateOrgRepo(org string, opt CreateRepoOption) (*Repository, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + repo := new(Repository) + return repo, c.getParsedResponse("POST", fmt.Sprintf("/org/%s/repos", org), jsonHeader, bytes.NewReader(body), repo) +} + +// GetRepo returns information of a repository of given owner. +func (c *Client) GetRepo(owner, reponame string) (*Repository, error) { + repo := new(Repository) + return repo, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s", owner, reponame), nil, nil, repo) +} + +// DeleteRepo deletes a repository of user or organization. +func (c *Client) DeleteRepo(owner, repo string) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s", owner, repo), nil, nil) + return err +} + +// MigrateRepoOption options when migrate repository from an external place +type MigrateRepoOption struct { + CloneAddr string `json:"clone_addr" binding:"Required"` + AuthUsername string `json:"auth_username"` + AuthPassword string `json:"auth_password"` + UID int `json:"uid" binding:"Required"` + RepoName string `json:"repo_name" binding:"Required"` + Mirror bool `json:"mirror"` + Private bool `json:"private"` + Description string `json:"description"` +} + +// MigrateRepo migrates a repository from other Git hosting sources for the +// authenticated user. +// +// To migrate a repository for a organization, the authenticated user must be a +// owner of the specified organization. +func (c *Client) MigrateRepo(opt MigrateRepoOption) (*Repository, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + repo := new(Repository) + return repo, c.getParsedResponse("POST", "/repos/migrate", jsonHeader, bytes.NewReader(body), repo) +} + +// MirrorSync adds a mirrored repository to the mirror sync queue. +func (c *Client) MirrorSync(owner, repo string) error { + _, err := c.getResponse("POST", fmt.Sprintf("/repos/%s/%s/mirror-sync", owner, repo), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_branch.go b/vendor/code.gitea.io/sdk/gitea/repo_branch.go new file mode 100644 index 000000000..fadc9e395 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_branch.go @@ -0,0 +1,27 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// Branch represents a repository branch. +type Branch struct { + Name string `json:"name"` + Commit *PayloadCommit `json:"commit"` +} + +// ListRepoBranches list all the branches of one repository +func (c *Client) ListRepoBranches(user, repo string) ([]*Branch, error) { + branches := make([]*Branch, 0, 10) + return branches, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches", user, repo), nil, nil, &branches) +} + +// GetRepoBranch get one branch's information of one repository +func (c *Client) GetRepoBranch(user, repo, branch string) (*Branch, error) { + b := new(Branch) + return b, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/branches/%s", user, repo, branch), nil, nil, &b) +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go b/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go new file mode 100644 index 000000000..546f2476e --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_collaborator.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// ListCollaborators list a repository's collaborators +func (c *Client) ListCollaborators(user, repo string) ([]*User, error) { + collaborators := make([]*User, 0, 10) + err := c.getParsedResponse("GET", + fmt.Sprintf("/repos/%s/%s/collaborators", user, repo), + nil, nil, &collaborators) + return collaborators, err +} + +// IsCollaborator check if a user is a collaborator of a repository +func (c *Client) IsCollaborator(user, repo, collaborator string) (bool, error) { + status, err := c.getStatusCode("GET", + fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), + nil, nil) + if err != nil { + return false, err + } + if status == 204 { + return true, nil + } + return false, nil +} + +// AddCollaboratorOption options when add some user as a collaborator of a repository +type AddCollaboratorOption struct { + Permission *string `json:"permission"` +} + +// AddCollaborator add some user as a collaborator of a repository +func (c *Client) AddCollaborator(user, repo, collaborator string, opt AddCollaboratorOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("PUT", fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), nil, bytes.NewReader(body)) + return err +} + +// DeleteCollaborator remove a collaborator from a repository +func (c *Client) DeleteCollaborator(user, repo, collaborator string) error { + _, err := c.getResponse("DELETE", + fmt.Sprintf("/repos/%s/%s/collaborators/%s", user, repo, collaborator), + nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_file.go b/vendor/code.gitea.io/sdk/gitea/repo_file.go new file mode 100644 index 000000000..e6c89f0c0 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_file.go @@ -0,0 +1,15 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" +) + +// GetFile downloads a file of repository, ref can be branch/tag/commit. +// e.g.: ref -> master, tree -> macaron.go(no leading slash) +func (c *Client) GetFile(user, repo, ref, tree string) ([]byte, error) { + return c.getResponse("GET", fmt.Sprintf("/repos/%s/%s/raw/%s/%s", user, repo, ref, tree), nil, nil) +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_key.go b/vendor/code.gitea.io/sdk/gitea/repo_key.go new file mode 100644 index 000000000..24d29a209 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_key.go @@ -0,0 +1,67 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// DeployKey a deploy key +type DeployKey struct { + ID int64 `json:"id"` + Key string `json:"key"` + URL string `json:"url"` + Title string `json:"title"` + Created time.Time `json:"created_at"` + ReadOnly bool `json:"read_only"` +} + +// ListDeployKeys list all the deploy keys of one repository +func (c *Client) ListDeployKeys(user, repo string) ([]*DeployKey, error) { + keys := make([]*DeployKey, 0, 10) + return keys, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys", user, repo), nil, nil, &keys) +} + +// GetDeployKey get one deploy key with key id +func (c *Client) GetDeployKey(user, repo string, keyID int64) (*DeployKey, error) { + key := new(DeployKey) + return key, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/keys/%d", user, repo, keyID), nil, nil, &key) +} + +// CreateKeyOption options when create deploy key +// swagger:parameters userCurrentPostKey +type CreateKeyOption struct { + // Title of the key to add + // + // in: body + // required: true + // unique: true + Title string `json:"title" binding:"Required"` + // An armored SSH key to add + // + // in: body + // required: true + // unique: true + Key string `json:"key" binding:"Required"` +} + +// CreateDeployKey options when create one deploy key +func (c *Client) CreateDeployKey(user, repo string, opt CreateKeyOption) (*DeployKey, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + key := new(DeployKey) + return key, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/keys", user, repo), jsonHeader, bytes.NewReader(body), key) +} + +// DeleteDeployKey delete deploy key with key id +func (c *Client) DeleteDeployKey(owner, repo string, keyID int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/keys/%d", owner, repo, keyID), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/repo_watch.go b/vendor/code.gitea.io/sdk/gitea/repo_watch.go new file mode 100644 index 000000000..02c5d9b0d --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/repo_watch.go @@ -0,0 +1,42 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "fmt" + "net/http" + "time" +) + +// WatchInfo represents a API watch status of one repository +// swagger:response WatchInfo +type WatchInfo struct { + Subscribed bool `json:"subscribed"` + Ignored bool `json:"ignored"` + Reason interface{} `json:"reason"` + CreatedAt time.Time `json:"created_at"` + URL string `json:"url"` + RepositoryURL string `json:"repository_url"` +} + +// GetWatchedRepos list all the watched repos of user +func (c *Client) GetWatchedRepos(user, pass string) ([]*Repository, error) { + repos := make([]*Repository, 0, 10) + return repos, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/subscriptions", user), + http.Header{"Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, nil, &repos) +} + +// WatchRepo start to watch a repository +func (c *Client) WatchRepo(user, pass, repoUser, repoName string) (*WatchInfo, error) { + i := new(WatchInfo) + return i, c.getParsedResponse("PUT", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), + http.Header{"Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, nil, i) +} + +// UnWatchRepo start to watch a repository +func (c *Client) UnWatchRepo(user, pass, repoUser, repoName string) (int, error) { + return c.getStatusCode("DELETE", fmt.Sprintf("/repos/%s/%s/subscription", repoUser, repoName), + http.Header{"Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, nil) +} diff --git a/vendor/code.gitea.io/sdk/gitea/status.go b/vendor/code.gitea.io/sdk/gitea/status.go new file mode 100644 index 000000000..e694add1c --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/status.go @@ -0,0 +1,95 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// StatusState holds the state of a Status +// It can be "pending", "success", "error", "failure", and "warning" +type StatusState string + +const ( + // StatusPending is for when the Status is Pending + StatusPending StatusState = "pending" + // StatusSuccess is for when the Status is Success + StatusSuccess StatusState = "success" + // StatusError is for when the Status is Error + StatusError StatusState = "error" + // StatusFailure is for when the Status is Failure + StatusFailure StatusState = "failure" + // StatusWarning is for when the Status is Warning + StatusWarning StatusState = "warning" +) + +// Status holds a single Status of a single Commit +type Status struct { + ID int64 `json:"id"` + State StatusState `json:"status"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + URL string `json:"url"` + Context string `json:"context"` + Creator *User `json:"creator"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"updated_at"` +} + +// CombinedStatus holds the combined state of several statuses for a single commit +type CombinedStatus struct { + State StatusState `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []*Status `json:"statuses"` + Repository *Repository `json:"repository"` + CommitURL string `json:"commit_url"` + URL string `json:"url"` +} + +// CreateStatusOption holds the information needed to create a new Status for a Commit +type CreateStatusOption struct { + State StatusState `json:"state"` + TargetURL string `json:"target_url"` + Description string `json:"description"` + Context string `json:"context"` +} + +// ListStatusesOption holds pagination information +type ListStatusesOption struct { + Page int +} + +// CreateStatus creates a new Status for a given Commit +// +// POST /repos/:owner/:repo/statuses/:sha +func (c *Client) CreateStatus(owner, repo, sha string, opts CreateStatusOption) (*Status, error) { + body, err := json.Marshal(&opts) + if err != nil { + return nil, err + } + status := &Status{} + return status, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/statuses/%s", owner, repo, sha), + jsonHeader, bytes.NewReader(body), status) +} + +// ListStatuses returns all statuses for a given Commit +// +// GET /repos/:owner/:repo/commits/:ref/statuses +func (c *Client) ListStatuses(owner, repo, sha string, opts ListStatusesOption) ([]*Status, error) { + statuses := make([]*Status, 0, 10) + return statuses, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/statuses?page=%d", owner, repo, sha, opts.Page), nil, nil, &statuses) +} + +// GetCombinedStatus returns the CombinedStatus for a given Commit +// +// GET /repos/:owner/:repo/commits/:ref/status +func (c *Client) GetCombinedStatus(owner, repo, sha string) (*CombinedStatus, error) { + status := &CombinedStatus{} + return status, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/commits/%s/status", owner, repo, sha), nil, nil, status) +} diff --git a/vendor/code.gitea.io/sdk/gitea/user.go b/vendor/code.gitea.io/sdk/gitea/user.go new file mode 100644 index 000000000..9fe2edcbf --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user.go @@ -0,0 +1,41 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "encoding/json" + "fmt" +) + +// User represents a API user. +// swagger:response User +type User struct { + ID int64 `json:"id"` + UserName string `json:"login"` + FullName string `json:"full_name"` + Email string `json:"email"` + AvatarURL string `json:"avatar_url"` +} + +// UserList represents a list of API user. +// swagger:response UserList +type UserList []*User + +// MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility +func (u User) MarshalJSON() ([]byte, error) { + // Re-declaring User to avoid recursion + type shadow User + return json.Marshal(struct { + shadow + CompatUserName string `json:"username"` + }{shadow(u), u.UserName}) +} + +// GetUserInfo get user info by user's name +func (c *Client) GetUserInfo(user string) (*User, error) { + u := new(User) + err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u) + return u, err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_app.go b/vendor/code.gitea.io/sdk/gitea/user_app.go new file mode 100644 index 000000000..82d2a4046 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_app.go @@ -0,0 +1,56 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" +) + +// BasicAuthEncode generate base64 of basic auth head +func BasicAuthEncode(user, pass string) string { + return base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)) +} + +// AccessToken represents a API access token. +// swagger:response AccessToken +type AccessToken struct { + Name string `json:"name"` + Sha1 string `json:"sha1"` +} + +// AccessTokenList represents a list of API access token. +// swagger:response AccessTokenList +type AccessTokenList []*AccessToken + +// ListAccessTokens lista all the access tokens of user +func (c *Client) ListAccessTokens(user, pass string) ([]*AccessToken, error) { + tokens := make([]*AccessToken, 0, 10) + return tokens, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/tokens", user), + http.Header{"Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, nil, &tokens) +} + +// CreateAccessTokenOption options when create access token +// swagger:parameters userCreateToken +type CreateAccessTokenOption struct { + Name string `json:"name" binding:"Required"` +} + +// CreateAccessToken create one access token with options +func (c *Client) CreateAccessToken(user, pass string, opt CreateAccessTokenOption) (*AccessToken, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + t := new(AccessToken) + return t, c.getParsedResponse("POST", fmt.Sprintf("/users/%s/tokens", user), + http.Header{ + "content-type": []string{"application/json"}, + "Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, + bytes.NewReader(body), t) +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_email.go b/vendor/code.gitea.io/sdk/gitea/user_email.go new file mode 100644 index 000000000..e167b5dfb --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_email.go @@ -0,0 +1,48 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" +) + +// Email en email information of user +type Email struct { + Email string `json:"email"` + Verified bool `json:"verified"` + Primary bool `json:"primary"` +} + +// ListEmails all the email addresses of user +func (c *Client) ListEmails() ([]*Email, error) { + emails := make([]*Email, 0, 3) + return emails, c.getParsedResponse("GET", "/user/emails", nil, nil, &emails) +} + +// CreateEmailOption options when create an email +type CreateEmailOption struct { + Emails []string `json:"emails"` +} + +// AddEmail add one email to current user with options +func (c *Client) AddEmail(opt CreateEmailOption) ([]*Email, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + emails := make([]*Email, 0, 3) + return emails, c.getParsedResponse("POST", "/user/emails", jsonHeader, bytes.NewReader(body), emails) +} + +// DeleteEmail delete one email of current users' +func (c *Client) DeleteEmail(opt CreateEmailOption) error { + body, err := json.Marshal(&opt) + if err != nil { + return err + } + _, err = c.getResponse("DELETE", "/user/emails", jsonHeader, bytes.NewReader(body)) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_follow.go b/vendor/code.gitea.io/sdk/gitea/user_follow.go new file mode 100644 index 000000000..a197a7f18 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_follow.go @@ -0,0 +1,55 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import "fmt" + +// ListMyFollowers list all the followers of current user +func (c *Client) ListMyFollowers(page int) ([]*User, error) { + users := make([]*User, 0, 10) + return users, c.getParsedResponse("GET", fmt.Sprintf("/user/followers?page=%d", page), nil, nil, &users) +} + +// ListFollowers list all the followers of one user +func (c *Client) ListFollowers(user string, page int) ([]*User, error) { + users := make([]*User, 0, 10) + return users, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/followers?page=%d", user, page), nil, nil, &users) +} + +// ListMyFollowing list all the users current user followed +func (c *Client) ListMyFollowing(page int) ([]*User, error) { + users := make([]*User, 0, 10) + return users, c.getParsedResponse("GET", fmt.Sprintf("/user/following?page=%d", page), nil, nil, &users) +} + +// ListFollowing list all the users the user followed +func (c *Client) ListFollowing(user string, page int) ([]*User, error) { + users := make([]*User, 0, 10) + return users, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/following?page=%d", user, page), nil, nil, &users) +} + +// IsFollowing if current user followed the target +func (c *Client) IsFollowing(target string) bool { + _, err := c.getResponse("GET", fmt.Sprintf("/user/following/%s", target), nil, nil) + return err == nil +} + +// IsUserFollowing if the user followed the target +func (c *Client) IsUserFollowing(user, target string) bool { + _, err := c.getResponse("GET", fmt.Sprintf("/users/%s/following/%s", user, target), nil, nil) + return err == nil +} + +// Follow set current user follow the target +func (c *Client) Follow(target string) error { + _, err := c.getResponse("PUT", fmt.Sprintf("/user/following/%s", target), nil, nil) + return err +} + +// Unfollow set current user unfollow the target +func (c *Client) Unfollow(target string) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/user/following/%s", target), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go new file mode 100644 index 000000000..942478b9f --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_gpgkey.go @@ -0,0 +1,85 @@ +// Copyright 2017 Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// GPGKeyList represents a list of GPGKey +// swagger:response GPGKeyList +type GPGKeyList []*GPGKey + +// GPGKey a user GPG key to sign commit and tag in repository +// swagger:response GPGKey +type GPGKey struct { + ID int64 `json:"id"` + PrimaryKeyID string `json:"primary_key_id"` + KeyID string `json:"key_id"` + PublicKey string `json:"public_key"` + Emails []*GPGKeyEmail `json:"emails"` + SubsKey []*GPGKey `json:"subkeys"` + CanSign bool `json:"can_sign"` + CanEncryptComms bool `json:"can_encrypt_comms"` + CanEncryptStorage bool `json:"can_encrypt_storage"` + CanCertify bool `json:"can_certify"` + Created time.Time `json:"created_at,omitempty"` + Expires time.Time `json:"expires_at,omitempty"` +} + +// GPGKeyEmail a email attache to a GPGKey +// swagger:model GPGKeyEmail +type GPGKeyEmail struct { + Email string `json:"email"` + Verified bool `json:"verified"` +} + +// CreateGPGKeyOption options create user GPG key +// swagger:parameters userCurrentPostGPGKey +type CreateGPGKeyOption struct { + // An armored GPG key to add + // + // in: body + // required: true + // unique: true + ArmoredKey string `json:"armored_public_key" binding:"Required"` +} + +// ListGPGKeys list all the GPG keys of the user +func (c *Client) ListGPGKeys(user string) ([]*GPGKey, error) { + keys := make([]*GPGKey, 0, 10) + return keys, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/gpg_keys", user), nil, nil, &keys) +} + +// ListMyGPGKeys list all the GPG keys of current user +func (c *Client) ListMyGPGKeys() ([]*GPGKey, error) { + keys := make([]*GPGKey, 0, 10) + return keys, c.getParsedResponse("GET", "/user/gpg_keys", nil, nil, &keys) +} + +// GetGPGKey get current user's GPG key by key id +func (c *Client) GetGPGKey(keyID int64) (*GPGKey, error) { + key := new(GPGKey) + return key, c.getParsedResponse("GET", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil, &key) +} + +// CreateGPGKey create GPG key with options +func (c *Client) CreateGPGKey(opt CreateGPGKeyOption) (*GPGKey, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + key := new(GPGKey) + return key, c.getParsedResponse("POST", "/user/gpg_keys", jsonHeader, bytes.NewReader(body), key) +} + +// DeleteGPGKey delete GPG key with key id +func (c *Client) DeleteGPGKey(keyID int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/user/gpg_keys/%d", keyID), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_key.go b/vendor/code.gitea.io/sdk/gitea/user_key.go new file mode 100644 index 000000000..397c6d17a --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/user_key.go @@ -0,0 +1,60 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "bytes" + "encoding/json" + "fmt" + "time" +) + +// PublicKeyList represents a list of PublicKey +// swagger:response PublicKeyList +type PublicKeyList []*PublicKey + +// PublicKey publickey is a user key to push code to repository +// swagger:response PublicKey +type PublicKey struct { + ID int64 `json:"id"` + Key string `json:"key"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Created time.Time `json:"created_at,omitempty"` +} + +// ListPublicKeys list all the public keys of the user +func (c *Client) ListPublicKeys(user string) ([]*PublicKey, error) { + keys := make([]*PublicKey, 0, 10) + return keys, c.getParsedResponse("GET", fmt.Sprintf("/users/%s/keys", user), nil, nil, &keys) +} + +// ListMyPublicKeys list all the public keys of current user +func (c *Client) ListMyPublicKeys() ([]*PublicKey, error) { + keys := make([]*PublicKey, 0, 10) + return keys, c.getParsedResponse("GET", "/user/keys", nil, nil, &keys) +} + +// GetPublicKey get current user's public key by key id +func (c *Client) GetPublicKey(keyID int64) (*PublicKey, error) { + key := new(PublicKey) + return key, c.getParsedResponse("GET", fmt.Sprintf("/user/keys/%d", keyID), nil, nil, &key) +} + +// CreatePublicKey create public key with options +func (c *Client) CreatePublicKey(opt CreateKeyOption) (*PublicKey, error) { + body, err := json.Marshal(&opt) + if err != nil { + return nil, err + } + key := new(PublicKey) + return key, c.getParsedResponse("POST", "/user/keys", jsonHeader, bytes.NewReader(body), key) +} + +// DeletePublicKey delete public key with key id +func (c *Client) DeletePublicKey(keyID int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/user/keys/%d", keyID), nil, nil) + return err +} diff --git a/vendor/code.gitea.io/sdk/gitea/utils.go b/vendor/code.gitea.io/sdk/gitea/utils.go new file mode 100644 index 000000000..80892a1e7 --- /dev/null +++ b/vendor/code.gitea.io/sdk/gitea/utils.go @@ -0,0 +1,26 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package gitea + +import ( + "net/http" +) + +var jsonHeader = http.Header{"content-type": []string{"application/json"}} + +// Bool return address of bool value +func Bool(v bool) *bool { + return &v +} + +// String return address of string value +func String(v string) *string { + return &v +} + +// Int64 return address of int64 value +func Int64(v int64) *int64 { + return &v +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 7d92bae2f..34d812257 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -6,6 +6,12 @@ "path": "appengine/cloudsql", "revision": "" }, + { + "checksumSHA1": "nLhT+bLMj8uLICP+EZbrdoQe6mM=", + "path": "code.gitea.io/sdk/gitea", + "revision": "8cff72208aa458f4efa8fdfbad29b03aee485b8c", + "revisionTime": "2017-05-06T01:37:21Z" + }, { "checksumSHA1": "zTn0jzjOiJlScR1px66MvrgrlLs=", "origin": "github.com/docker/docker/vendor/github.com/Microsoft/go-winio",