diff --git a/remote/bitbucket/bitbucket.go b/remote/bitbucket/bitbucket.go index 5a61eab63..143d2aff1 100644 --- a/remote/bitbucket/bitbucket.go +++ b/remote/bitbucket/bitbucket.go @@ -209,6 +209,10 @@ func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return []byte(config.Data), err } +func (c *config) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Status creates a build status for the Bitbucket commit. func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { status := internal.BuildStatus{ diff --git a/remote/bitbucketserver/bitbucketserver.go b/remote/bitbucketserver/bitbucketserver.go index 0b394821f..9e14a377a 100644 --- a/remote/bitbucketserver/bitbucketserver.go +++ b/remote/bitbucketserver/bitbucketserver.go @@ -179,6 +179,10 @@ func (c *Config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return client.FindFileForRepo(r.Owner, r.Name, f, b.Ref) } +func (c *Config) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Status is not supported by the bitbucketserver driver. func (c *Config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { status := internal.BuildStatus{ diff --git a/remote/coding/coding.go b/remote/coding/coding.go index 1d2229b13..e488bc2c9 100644 --- a/remote/coding/coding.go +++ b/remote/coding/coding.go @@ -238,6 +238,10 @@ func (c *Coding) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return data, nil } +func (c *Coding) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Status sends the commit status to the remote system. func (c *Coding) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { // EMPTY: not implemented in Coding OAuth API diff --git a/remote/gerrit/gerrit.go b/remote/gerrit/gerrit.go index c8f926108..a78b9d530 100644 --- a/remote/gerrit/gerrit.go +++ b/remote/gerrit/gerrit.go @@ -103,6 +103,10 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return nil, nil } +func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Status is not supported by the Gogs driver. func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { return nil diff --git a/remote/gitea/gitea.go b/remote/gitea/gitea.go index 39a5105d6..9efaca9e2 100644 --- a/remote/gitea/gitea.go +++ b/remote/gitea/gitea.go @@ -249,6 +249,10 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return cfg, err } +func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // 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) diff --git a/remote/github/github.go b/remote/github/github.go index 1fd057892..355a8accc 100644 --- a/remote/github/github.go +++ b/remote/github/github.go @@ -236,6 +236,31 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return data.Decode() } +func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + client := c.newClientToken(u.Token) + + opts := new(github.RepositoryContentGetOptions) + opts.Ref = b.Commit + _, data, _, err := client.Repositories.GetContents(r.Owner, r.Name, f, opts) + if err != nil { + return nil, err + } + + var files []*remote.FileMeta + for _, file := range data { + data, err := file.Decode() + if err != nil { + return nil, err + } + files = append(files, &remote.FileMeta{ + Name: *file.Name, + Data: data, + }) + } + + return files, nil +} + // Netrc returns a netrc file capable of authenticating GitHub requests and // cloning GitHub repositories. The netrc will use the global machine account // when configured. diff --git a/remote/gitlab/gitlab.go b/remote/gitlab/gitlab.go index e61776b79..a0e60725b 100644 --- a/remote/gitlab/gitlab.go +++ b/remote/gitlab/gitlab.go @@ -338,6 +338,10 @@ func (g *Gitlab) File(user *model.User, repo *model.Repo, build *model.Build, f return out, err } +func (c *Gitlab) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // NOTE Currently gitlab doesn't support status for commits and events, // also if we want get MR status in gitlab we need implement a special plugin for gitlab, // gitlab uses API to fetch build status on client side. But for now we skip this. diff --git a/remote/gitlab3/gitlab.go b/remote/gitlab3/gitlab.go index 7a0dde3d8..7009271ee 100644 --- a/remote/gitlab3/gitlab.go +++ b/remote/gitlab3/gitlab.go @@ -338,6 +338,10 @@ func (g *Gitlab) File(user *model.User, repo *model.Repo, build *model.Build, f return out, err } +func (c *Gitlab) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // NOTE Currently gitlab doesn't support status for commits and events, // also if we want get MR status in gitlab we need implement a special plugin for gitlab, // gitlab uses API to fetch build status on client side. But for now we skip this. diff --git a/remote/gogs/gogs.go b/remote/gogs/gogs.go index 13072c0ca..f6d1aa7ca 100644 --- a/remote/gogs/gogs.go +++ b/remote/gogs/gogs.go @@ -202,6 +202,10 @@ func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([ return cfg, err } +func (c *client) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Status is not supported by the Gogs driver. func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { return nil diff --git a/remote/mock/remote.go b/remote/mock/remote.go index 97577c73f..aaf8a7442 100644 --- a/remote/mock/remote.go +++ b/remote/mock/remote.go @@ -15,9 +15,11 @@ package mock import ( + "fmt" "net/http" "github.com/laszlocph/drone-oss-08/model" + "github.com/laszlocph/drone-oss-08/remote" "github.com/stretchr/testify/mock" ) @@ -98,6 +100,10 @@ func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ( return r0, r1 } +func (c *Remote) Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*remote.FileMeta, error) { + return nil, fmt.Errorf("Not implemented") +} + // Hook provides a mock function with given fields: r func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) { ret := _m.Called(r) diff --git a/remote/remote.go b/remote/remote.go index 297a77aad..070882d6e 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -51,6 +51,9 @@ type Remote interface { // format. File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) + // Dir fetches a folder from the remote repository + Dir(u *model.User, r *model.Repo, b *model.Build, f string) ([]*FileMeta, error) + // Status sends the commit status to the remote system. // An example would be the GitHub pull request status. Status(u *model.User, r *model.Repo, b *model.Build, link string) error @@ -71,6 +74,12 @@ type Remote interface { Hook(r *http.Request) (*model.Repo, *model.Build, error) } +// FileMeta represents a file in version control +type FileMeta struct { + Name string + Data []byte +} + // Refresher refreshes an oauth token and expiration for the given user. It // returns true if the token was refreshed, false if the token was not refreshed, // and error if it failed to refersh. @@ -166,3 +175,17 @@ func FileBackoff(remote Remote, u *model.User, r *model.Repo, b *model.Build, f } return } + +// DirBackoff fetches the folder using an exponential backoff. +func DirBackoff(remote Remote, u *model.User, r *model.Repo, b *model.Build, f string) (out []*FileMeta, err error) { + for i := 0; i < 5; i++ { + select { + case <-time.After(time.Second * time.Duration(i)): + out, err = remote.Dir(u, r, b, f) + if err == nil { + return + } + } + } + return +}