diff --git a/scm/content.go b/scm/content.go index 439a83f81..7d4a567e3 100644 --- a/scm/content.go +++ b/scm/content.go @@ -22,11 +22,24 @@ type ( Data []byte } + // FileEntry returns the details of a file + FileEntry struct { + Name string + Path string + Type string + Size int + Sha string + Link string + } + // ContentService provides access to repositroy content. ContentService interface { // Find returns the repository file content by path. Find(ctx context.Context, repo, path, ref string) (*Content, *Response, error) + // Lists the files or directories at the given path + List(ctx context.Context, repo, path, ref string) ([]*FileEntry, *Response, error) + // Create creates a new repositroy file. Create(ctx context.Context, repo, path string, params *ContentParams) (*Response, error) diff --git a/scm/driver/bitbucket/content.go b/scm/driver/bitbucket/content.go index 75053c274..ab4de794d 100644 --- a/scm/driver/bitbucket/content.go +++ b/scm/driver/bitbucket/content.go @@ -26,6 +26,10 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } diff --git a/scm/driver/gitea/content.go b/scm/driver/gitea/content.go index 0c9b18d27..11dfb124f 100644 --- a/scm/driver/gitea/content.go +++ b/scm/driver/gitea/content.go @@ -29,6 +29,10 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } diff --git a/scm/driver/github/content.go b/scm/driver/github/content.go index 2656541b9..b9234b8fd 100644 --- a/scm/driver/github/content.go +++ b/scm/driver/github/content.go @@ -28,6 +28,13 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + endpoint := fmt.Sprintf("repos/%s/contents/%s?ref=%s", repo, path, ref) + out := []*entry{} + res, err := s.client.do(ctx, "GET", endpoint, nil, &out) + return convertEntryList(out), res, err +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } @@ -47,6 +54,15 @@ type content struct { Content string `json:"content"` } +type entry struct { + Name string `json:"name"` + Type string `json:"type"` + Path string `json:"path"` + Size int `json:"size"` + Sha string `json:"sha"` + URL string `json:"url"` +} + type contentUpdate struct { Sha string `json:"sha"` Message string `json:"message"` @@ -62,3 +78,22 @@ type contentUpdate struct { Date time.Time `json:"date"` } `json:"committer"` } + +func convertEntryList(out []*entry) []*scm.FileEntry { + answer := make([]*scm.FileEntry, 0, len(out)) + for _, o := range out { + answer = append(answer, convertEntry(o)) + } + return answer +} + +func convertEntry(from *entry) *scm.FileEntry { + return &scm.FileEntry{ + Name: from.Name, + Path: from.Path, + Type: from.Type, + Size: from.Size, + Sha: from.Sha, + Link: from.URL, + } +} diff --git a/scm/driver/github/content_test.go b/scm/driver/github/content_test.go index 6809fa81f..98ede9d39 100644 --- a/scm/driver/github/content_test.go +++ b/scm/driver/github/content_test.go @@ -51,6 +51,47 @@ func TestContentFind(t *testing.T) { t.Run("Rate", testRate(res)) } +func TestContentList(t *testing.T) { + defer gock.Off() + + gock.New("https://api.github.com"). + Get("/repos/octocat/hello-world/contents/README"). + MatchParam("ref", "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d"). + Reply(200). + Type("application/json"). + SetHeaders(mockHeaders). + File("testdata/content_list.json") + + client := NewDefault() + got, res, err := client.Contents.List( + context.Background(), + "octocat/hello-world", + "README", + "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", + ) + if err != nil { + t.Error(err) + return + } + + want := []*scm.FileEntry{} + raw, _ := ioutil.ReadFile("testdata/content_list.json.golden") + json.Unmarshal(raw, &want) + + if diff := cmp.Diff(got, want); diff != "" { + t.Errorf("Unexpected Results") + t.Log(diff) + + data, err := json.Marshal(got) + if err == nil { + t.Logf("got JSON: %s", string(data)) + } + } + + t.Run("Request", testRequest(res)) + t.Run("Rate", testRate(res)) +} + func TestContentCreate(t *testing.T) { content := new(contentService) _, err := content.Create(context.Background(), "octocat/hello-world", "README", nil) diff --git a/scm/driver/github/testdata/content_list.json b/scm/driver/github/testdata/content_list.json new file mode 100644 index 000000000..d4ffac0ad --- /dev/null +++ b/scm/driver/github/testdata/content_list.json @@ -0,0 +1,34 @@ +[ + { + "type": "file", + "size": 625, + "name": "octokit.rb", + "path": "lib/octokit.rb", + "sha": "fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", + "url": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb", + "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", + "html_url": "https://github.com/octokit/octokit.rb/blob/master/lib/octokit.rb", + "download_url": "https://raw.githubusercontent.com/octokit/octokit.rb/master/lib/octokit.rb", + "_links": { + "self": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb", + "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", + "html": "https://github.com/octokit/octokit.rb/blob/master/lib/octokit.rb" + } + }, + { + "type": "dir", + "size": 0, + "name": "octokit", + "path": "lib/octokit", + "sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "url": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit", + "git_url": "https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "html_url": "https://github.com/octokit/octokit.rb/tree/master/lib/octokit", + "download_url": null, + "_links": { + "self": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit", + "git": "https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "html": "https://github.com/octokit/octokit.rb/tree/master/lib/octokit" + } + } +] \ No newline at end of file diff --git a/scm/driver/github/testdata/content_list.json.golden b/scm/driver/github/testdata/content_list.json.golden new file mode 100644 index 000000000..9a0e9fd15 --- /dev/null +++ b/scm/driver/github/testdata/content_list.json.golden @@ -0,0 +1,18 @@ +[ + { + "Name": "octokit.rb", + "Path": "lib/octokit.rb", + "Type": "file", + "Size": 625, + "Sha": "fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b", + "Link": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb" + }, + { + "Name": "octokit", + "Path": "lib/octokit", + "Type": "dir", + "Size": 0, + "Sha": "a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d", + "Link": "https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit" + } +] \ No newline at end of file diff --git a/scm/driver/gitlab/content.go b/scm/driver/gitlab/content.go index 047d74352..ca1442cc1 100644 --- a/scm/driver/gitlab/content.go +++ b/scm/driver/gitlab/content.go @@ -39,6 +39,10 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } diff --git a/scm/driver/gogs/content.go b/scm/driver/gogs/content.go index d9d3ea405..d02d5940f 100644 --- a/scm/driver/gogs/content.go +++ b/scm/driver/gogs/content.go @@ -29,6 +29,10 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } diff --git a/scm/driver/stash/content.go b/scm/driver/stash/content.go index 8ad2ede02..f6e1eecf6 100644 --- a/scm/driver/stash/content.go +++ b/scm/driver/stash/content.go @@ -27,6 +27,10 @@ func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm }, res, err } +func (s *contentService) List(ctx context.Context, repo, path, ref string) ([]*scm.FileEntry, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { return nil, scm.ErrNotSupported } diff --git a/scm/factory/examples/contentlist/main.go b/scm/factory/examples/contentlist/main.go new file mode 100644 index 000000000..1a82eccd6 --- /dev/null +++ b/scm/factory/examples/contentlist/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jenkins-x/go-scm/scm" + "github.com/jenkins-x/go-scm/scm/factory" + "github.com/jenkins-x/go-scm/scm/factory/examples/helpers" +) + +func main() { + client, err := factory.NewClientFromEnvironment() + if err != nil { + helpers.Fail(err) + return + } + + ctx := context.Background() + args := os.Args + if len(args) < 4 { + fmt.Printf("arguments: owner repository path\n") + return + } + + owner := args[1] + repo := args[2] + path := args[3] + ref := "master" + if len(args) > 4 { + ref = args[4] + } + + fullRepo := scm.Join(owner, repo) + + fmt.Printf("getting content for repository %s/%s and path: %s with ref: %s\n", owner, repo, path, ref) + files, _, err := client.Contents.List(ctx, fullRepo, path, ref) + if err != nil { + helpers.Fail(err) + return + } + fmt.Printf("found %d files\n", len(files)) + + for _, f := range files { + fmt.Printf("\t%s size: %d type %s\n", f.Path, f.Size, f.Type) + } +} + +func createListOptions() scm.ListOptions { + return scm.ListOptions{ + Size: 1000, + } +} diff --git a/scm/factory/examples/contents/main.go b/scm/factory/examples/contents/main.go new file mode 100644 index 000000000..bd22a20cb --- /dev/null +++ b/scm/factory/examples/contents/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jenkins-x/go-scm/scm" + "github.com/jenkins-x/go-scm/scm/factory" + "github.com/jenkins-x/go-scm/scm/factory/examples/helpers" +) + +func main() { + client, err := factory.NewClientFromEnvironment() + if err != nil { + helpers.Fail(err) + return + } + + ctx := context.Background() + args := os.Args + if len(args) < 4 { + fmt.Printf("arguments: owner repository path\n") + return + } + + owner := args[1] + repo := args[2] + path := args[3] + ref := "master" + if len(args) > 4 { + ref = args[4] + } + + fullRepo := scm.Join(owner, repo) + + fmt.Printf("getting content for repository %s/%s and path: %s with ref: %s\n", owner, repo, path, ref) + content, _, err := client.Contents.Find(ctx, fullRepo, path, ref) + if err != nil { + helpers.Fail(err) + return + } + + fmt.Printf("result path %s\n", content.Path) + fmt.Printf("%s\n", string(content.Data)) +} + +func createListOptions() scm.ListOptions { + return scm.ListOptions{ + Size: 1000, + } +}