Skip to content
This repository has been archived by the owner on Apr 26, 2021. It is now read-only.

Support ls-tree as an api in gandalf #111

Merged
merged 11 commits into from
Jul 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
go:
- 1.1.2
- 1.2.2
- 1.3
- tip
before_install:
- sudo apt-get update -qq
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

Alessandro Corbelli <[email protected]>
Andrews Medina <[email protected]>
Danilo Bardusco <[email protected]>
Bernardo Heynemann <[email protected]>
Danilo Bardusco <[email protected]> <[email protected]>
Flavia Missi <[email protected]>
Flávio Ribeiro <[email protected]>
Francisco Souza <[email protected]> <[email protected]>
Expand Down
32 changes: 31 additions & 1 deletion api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func HealthCheck(w http.ResponseWriter, r *http.Request) {

func GetFileContents(w http.ResponseWriter, r *http.Request) {
repo := r.URL.Query().Get(":name")
path := r.URL.Query().Get(":path")
path := r.URL.Query().Get("path")
ref := r.URL.Query().Get("ref")
if ref == "" {
ref = "master"
Expand Down Expand Up @@ -303,3 +303,33 @@ func GetArchive(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Expires", "Mon, 26 Jul 1997 05:00:00 GMT")
w.Write(contents)
}

func GetTree(w http.ResponseWriter, r *http.Request) {
repo := r.URL.Query().Get(":name")
path := r.URL.Query().Get("path")
ref := r.URL.Query().Get("ref")
if ref == "" {
ref = "master"
}
if path == "" {
path = "."
}
if repo == "" {
err := fmt.Errorf("Error when trying to obtain tree for path %s on ref %s of repository %s (repository is required).", path, ref, repo)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
tree, err := repository.GetTree(repo, ref, path)
if err != nil {
err := fmt.Errorf("Error when trying to obtain tree for path %s on ref %s of repository %s (%s).", path, ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
b, err := json.Marshal(tree)
if err != nil {
err := fmt.Errorf("Error when trying to obtain tree for path %s on ref %s of repository %s (%s).", path, ref, repo, err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Write(b)
}
138 changes: 133 additions & 5 deletions api/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ func (s *S) TestHealthcheck(c *gocheck.C) {
}

func (s *S) TestGetFileContents(c *gocheck.C) {
url := "/repository/repo/contents/README.txt?:name=repo&:path=README.txt"
url := "/repository/repo/contents?:name=repo&path=README.txt"
expected := "result"
repository.Retriever = &repository.MockContentRetriever{
ResultContents: []byte(expected),
Expand All @@ -699,7 +699,7 @@ func (s *S) TestGetFileContents(c *gocheck.C) {
}

func (s *S) TestGetFileContentsWithoutExtension(c *gocheck.C) {
url := "/repository/repo/contents/README?:name=repo&:path=README"
url := "/repository/repo/contents?:name=repo&path=README"
expected := "result"
repository.Retriever = &repository.MockContentRetriever{
ResultContents: []byte(expected),
Expand All @@ -718,7 +718,7 @@ func (s *S) TestGetFileContentsWithoutExtension(c *gocheck.C) {
}

func (s *S) TestGetFileContentsWithRef(c *gocheck.C) {
url := "/repository/repo/contents/README?:name=repo&:path=README.txt&ref=other"
url := "/repository/repo/contents?:name=repo&path=README.txt&ref=other"
expected := "result"
mockRetriever := repository.MockContentRetriever{
ResultContents: []byte(expected),
Expand All @@ -739,7 +739,7 @@ func (s *S) TestGetFileContentsWithRef(c *gocheck.C) {
}

func (s *S) TestGetFileContentsWhenCommandFails(c *gocheck.C) {
url := "/repository/repo/contents/README?:name=repo&:path=README.txt&ref=other"
url := "/repository/repo/contents?:name=repo&path=README.txt&ref=other"
outputError := fmt.Errorf("command error")
repository.Retriever = &repository.MockContentRetriever{
OutputError: outputError,
Expand All @@ -756,7 +756,7 @@ func (s *S) TestGetFileContentsWhenCommandFails(c *gocheck.C) {
}

func (s *S) TestGetFileContentsWhenNoRepository(c *gocheck.C) {
url := "/repository//contents/README?:name=&:path=README.txt&ref=other"
url := "/repository//contents?:name=&path=README.txt&ref=other"
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
Expand Down Expand Up @@ -843,3 +843,131 @@ func (s *S) TestGetArchive(c *gocheck.C) {
c.Assert(recorder.Header()["Pragma"][0], gocheck.Equals, "private")
c.Assert(recorder.Header()["Expires"][0], gocheck.Equals, "Mon, 26 Jul 1997 05:00:00 GMT")
}

func (s *S) TestGetTreeWithDefaultValues(c *gocheck.C) {
url := "/repository/repo/tree?:name=repo"
tree := make([]map[string]string, 1)
tree[0] = make(map[string]string)
tree[0]["permission"] = "333"
tree[0]["filetype"] = "blob"
tree[0]["hash"] = "123456"
tree[0]["path"] = "filename.txt"
tree[0]["rawPath"] = "raw/filename.txt"
mockRetriever := repository.MockContentRetriever{
Tree: tree,
}
repository.Retriever = &mockRetriever
defer func() {
repository.Retriever = nil
}()
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetTree(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusOK)
var obj []map[string]string
json.Unmarshal(recorder.Body.Bytes(), &obj)
c.Assert(len(obj), gocheck.Equals, 1)
c.Assert(obj[0]["permission"], gocheck.Equals, tree[0]["permission"])
c.Assert(obj[0]["filetype"], gocheck.Equals, tree[0]["filetype"])
c.Assert(obj[0]["hash"], gocheck.Equals, tree[0]["hash"])
c.Assert(obj[0]["path"], gocheck.Equals, tree[0]["path"])
c.Assert(obj[0]["rawPath"], gocheck.Equals, tree[0]["rawPath"])
c.Assert(mockRetriever.LastRef, gocheck.Equals, "master")
c.Assert(mockRetriever.LastPath, gocheck.Equals, ".")
}

func (s *S) TestGetTreeWithSpecificPath(c *gocheck.C) {
url := "/repository/repo/tree?:name=repo&path=/test"
tree := make([]map[string]string, 1)
tree[0] = make(map[string]string)
tree[0]["permission"] = "333"
tree[0]["filetype"] = "blob"
tree[0]["hash"] = "123456"
tree[0]["path"] = "/test/filename.txt"
tree[0]["rawPath"] = "/test/raw/filename.txt"
mockRetriever := repository.MockContentRetriever{
Tree: tree,
}
repository.Retriever = &mockRetriever
defer func() {
repository.Retriever = nil
}()
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetTree(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusOK)
var obj []map[string]string
json.Unmarshal(recorder.Body.Bytes(), &obj)
c.Assert(len(obj), gocheck.Equals, 1)
c.Assert(obj[0]["permission"], gocheck.Equals, tree[0]["permission"])
c.Assert(obj[0]["filetype"], gocheck.Equals, tree[0]["filetype"])
c.Assert(obj[0]["hash"], gocheck.Equals, tree[0]["hash"])
c.Assert(obj[0]["path"], gocheck.Equals, tree[0]["path"])
c.Assert(obj[0]["rawPath"], gocheck.Equals, tree[0]["rawPath"])
c.Assert(mockRetriever.LastRef, gocheck.Equals, "master")
c.Assert(mockRetriever.LastPath, gocheck.Equals, "/test")
}

func (s *S) TestGetTreeWithSpecificRef(c *gocheck.C) {
url := "/repository/repo/tree?:name=repo&path=/test&ref=1.1.1"
tree := make([]map[string]string, 1)
tree[0] = make(map[string]string)
tree[0]["permission"] = "333"
tree[0]["filetype"] = "blob"
tree[0]["hash"] = "123456"
tree[0]["path"] = "/test/filename.txt"
tree[0]["rawPath"] = "/test/raw/filename.txt"
mockRetriever := repository.MockContentRetriever{
Tree: tree,
}
repository.Retriever = &mockRetriever
defer func() {
repository.Retriever = nil
}()
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetTree(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusOK)
var obj []map[string]string
json.Unmarshal(recorder.Body.Bytes(), &obj)
c.Assert(len(obj), gocheck.Equals, 1)
c.Assert(obj[0]["permission"], gocheck.Equals, tree[0]["permission"])
c.Assert(obj[0]["filetype"], gocheck.Equals, tree[0]["filetype"])
c.Assert(obj[0]["hash"], gocheck.Equals, tree[0]["hash"])
c.Assert(obj[0]["path"], gocheck.Equals, tree[0]["path"])
c.Assert(obj[0]["rawPath"], gocheck.Equals, tree[0]["rawPath"])
c.Assert(mockRetriever.LastRef, gocheck.Equals, "1.1.1")
c.Assert(mockRetriever.LastPath, gocheck.Equals, "/test")
}

func (s *S) TestGetTreeWhenNoRepo(c *gocheck.C) {
url := "/repository//tree?:name="
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetTree(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusBadRequest)
expected := "Error when trying to obtain tree for path . on ref master of repository (repository is required).\n"
c.Assert(recorder.Body.String(), gocheck.Equals, expected)
}

func (s *S) TestGetTreeWhenCommandFails(c *gocheck.C) {
url := "/repository/repo/tree/?:name=repo&ref=master&path=/test"
expected := fmt.Errorf("output error")
mockRetriever := repository.MockContentRetriever{
OutputError: expected,
}
repository.Retriever = &mockRetriever
defer func() {
repository.Retriever = nil
}()
request, err := http.NewRequest("GET", url, nil)
c.Assert(err, gocheck.IsNil)
recorder := httptest.NewRecorder()
GetTree(recorder, request)
c.Assert(recorder.Code, gocheck.Equals, http.StatusBadRequest)
c.Assert(recorder.Body.String(), gocheck.Equals, "Error when trying to obtain tree for path /test on ref master of repository repo (output error).\n")
}
52 changes: 51 additions & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Get file contents
Returns the contents for a `path` in the specified `repository` with the given `ref` (commit, tag or branch).

* Method: GET
* URI: /repository/`:name`/contents/`:path`?ref=:ref
* URI: /repository/`:name`/contents?ref=:ref&path=:path
* Format: binary

Where:
Expand All @@ -65,6 +65,50 @@ Where:
* `:path` is the file path in the repository file system;
* `:ref` is the repository ref (commit, tag or branch). **This is optional**. If not passed this is assumed to be "master".

Example URLs (http://gandalf-server omitted for clarity)::

$ curl /repository/myrepository/contents?ref=0.1.0&path=/some/path/in/the/repo.txt
$ curl /repository/myrepository/contents?path=/some/path/in/the/repo.txt # gets master

Get tree
--------

Returns a list of all the files under a `path` in the specified `repository` with the given `ref` (commit, tag or branch).

* Method: GET
* URI: /repository/`:name`/tree?ref=:ref&path=:path
* Format: JSON

Where:

* `:name` is the name of the repository;
* `:path` is the file path in the repository file system. **This is optional**. If not passed this is assumed to be ".";
* `:ref` is the repository ref (commit, tag or branch). **This is optional**. If not passed this is assumed to be "master".

Example result::

[{
filetype: "blob",
hash: "6767b5de5943632e47cb6f8bf5b2147bc0be5cf8",
path: ".gitignore",
permission: "100644",
rawPath: ".gitignore"
}, {
filetype: "blob",
hash: "fbd8b6db62282a8402a4fc5503e9a886b4fb8b4b",
path: ".travis.yml",
permission: "100644",
rawPath: ".travis.yml"
}]

`rawPath` contains exactly the value returned from git (with escaped characters, quotes, etc), while `path` is somewhat cleaner (spaces removed, quotes removed from the left and right).

Example URLs (http://gandalf-server omitted for clarity)::

$ curl /repository/myrepository/tree # gets master and root path(.)
$ curl /repository/myrepository/tree?ref=0.1.0 # gets 0.1.0 tag and root path(.)
$ curl /repository/myrepository/tree?ref=0.1.0&path=/myrepository # gets 0.1.0 tag and files under /myrepository

Get archive
-----------

Expand All @@ -79,3 +123,9 @@ Where:
* `:name` is the name of the repository;
* `:ref` is the repository ref (commit, tag or branch);
* `:format` is the format to return the archive. This can be zip, tar or tar.gz.

Example URLs (http://gandalf-server omitted for clarity)::

$ curl /repository/myrepository/archive/master.zip # gets master and zip format
$ curl /repository/myrepository/archive/master.tar.gz # gets master and tar.gz format
$ curl /repository/myrepository/archive/0.1.0.zip # gets 0.1.0 tag and zip format
44 changes: 27 additions & 17 deletions docs/source/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
Configuring ganfalf
===================

Ganfalf uses a configuration file in `YAML <http://www.yaml.org/>`_ format. This
document describes what each option means, and how it should look like.
Ganfalf uses a configuration file in `YAML <http://www.yaml.org/>`_ format.
This document describes what each option means, and how it should look like.

Notation
========

Ganfalf uses a colon to represent nesting in YAML. So, whenever this document say
something like ``key1:key2``, it refers to the value of the ``key2`` that is
nested in the block that is the value of ``key1``. For example,
Ganfalf uses a colon to represent nesting in YAML. So, whenever this document
say something like ``key1:key2``, it refers to the value of the ``key2`` that
is nested in the block that is the value of ``key1``. For example,
``database:url`` means:

.. highlight:: yaml
Expand All @@ -23,14 +23,14 @@ nested in the block that is the value of ``key1``. For example,
Ganfalf configuration
=====================

This section describes gandalf's core configuration. Other sections will include
configuration of optional components, and finally, a full sample file.
This section describes gandalf's core configuration. Other sections will
include configuration of optional components, and finally, a full sample file.

HTTP server
-----------

Ganfalf provides a REST API, that supports HTTP and HTTP/TLS (a.k.a. HTTPS). Here
are the options that affect how gandalf's API behaves:
Gandalf provides a REST API, that supports HTTP and HTTP/TLS (a.k.a. HTTPS).
Here are the options that affect how gandalf's API behaves:

webserver:port
++++++++++++++
Expand All @@ -42,19 +42,29 @@ has no default value.
host
++++

``host`` is the value used to compose the remote URL for the repositories
managed by Gandalf. For example, if the repository is named "myapp" and the
host is "gandalf.mycompany.com", then the remote URL for the repository will be
``host`` is the value used to compose the remote URL for repositories managed
by Gandalf. For example, if the repository is named "myapp" and the host is
"gandalf.mycompany.com", then the remote URL for the repository will be
"[email protected]:myapp.git".

readonly-host
+++++++++++++

When specified, ``readonly-host`` is the value used to compose the readonly
remote URL for repositories managed by Gandalf. For example, if the repository
is named "myapp" and the host is "private.gandalf.mycompany.com", then the
readonly remote URL will be "git://private.gandalf.mycompany.com/myapp.git".

When ommited, ``host`` is used for composing the readonly remote URL.

Database access
---------------

Ganfalf uses MongoDB as database manager, to store information about users, VM's,
and its components. Regarding database control, you're able to define to which
database server gandalf will connect (providing a `MongoDB connection string
<http://docs.mongodb.org/manual/reference/connection-string/>`_). The database
related options are listed below:
Ganfalf uses MongoDB as database manager, to store information about users,
VM's, and its components. Regarding database control, you're able to define to
which database server gandalf will connect (providing a `MongoDB connection
string <http://docs.mongodb.org/manual/reference/connection-string/>`_). The
database related options are listed below:

database:url
++++++++++++
Expand Down
Loading