├── .gitignore ├── Dockerfile ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ ├── .gitignore │ └── src │ ├── github.com │ ├── CenturyLinkLabs │ │ └── docker-reg-client │ │ │ └── registry │ │ │ ├── auth.go │ │ │ ├── auth_test.go │ │ │ ├── doc.go │ │ │ ├── example_test.go │ │ │ ├── hub.go │ │ │ ├── hub_test.go │ │ │ ├── image.go │ │ │ ├── image_test.go │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ ├── repository.go │ │ │ ├── repository_test.go │ │ │ ├── search.go │ │ │ └── search_test.go │ ├── gorilla │ │ ├── context │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ ├── context_test.go │ │ │ └── doc.go │ │ └── mux │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bench_test.go │ │ │ ├── doc.go │ │ │ ├── mux.go │ │ │ ├── mux_test.go │ │ │ ├── old_test.go │ │ │ ├── regexp.go │ │ │ └── route.go │ ├── pmylund │ │ └── go-cache │ │ │ ├── CONTRIBUTORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cache.go │ │ │ ├── cache_test.go │ │ │ ├── sharded.go │ │ │ └── sharded_test.go │ ├── rs │ │ └── cors │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bench_test.go │ │ │ ├── cors.go │ │ │ ├── cors_test.go │ │ │ ├── examples │ │ │ ├── alice │ │ │ │ └── server.go │ │ │ ├── default │ │ │ │ └── server.go │ │ │ ├── goji │ │ │ │ └── server.go │ │ │ ├── martini │ │ │ │ └── server.go │ │ │ ├── negroni │ │ │ │ └── server.go │ │ │ ├── nethttp │ │ │ │ └── server.go │ │ │ └── openbar │ │ │ │ └── server.go │ │ │ ├── utils.go │ │ │ └── utils_test.go │ └── stretchr │ │ ├── objx │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── accessors.go │ │ ├── accessors_test.go │ │ ├── codegen │ │ │ ├── array-access.txt │ │ │ ├── index.html │ │ │ ├── template.txt │ │ │ └── types_list.txt │ │ ├── constants.go │ │ ├── conversions.go │ │ ├── conversions_test.go │ │ ├── doc.go │ │ ├── fixture_test.go │ │ ├── map.go │ │ ├── map_for_test.go │ │ ├── map_test.go │ │ ├── mutations.go │ │ ├── mutations_test.go │ │ ├── security.go │ │ ├── security_test.go │ │ ├── simple_example_test.go │ │ ├── tests.go │ │ ├── tests_test.go │ │ ├── type_specific_codegen.go │ │ ├── type_specific_codegen_test.go │ │ ├── value.go │ │ └── value_test.go │ │ └── testify │ │ ├── assert │ │ ├── assertions.go │ │ ├── assertions_test.go │ │ ├── doc.go │ │ ├── errors.go │ │ ├── forward_assertions.go │ │ ├── forward_assertions_test.go │ │ ├── http_assertions.go │ │ └── http_assertions_test.go │ │ └── mock │ │ ├── doc.go │ │ ├── mock.go │ │ └── mock_test.go │ ├── golang.org │ └── x │ │ └── net │ │ └── netutil │ │ ├── listen.go │ │ └── listen_test.go │ └── gopkg.in │ └── tylerb │ └── graceful.v1 │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── graceful.go │ ├── graceful_test.go │ ├── tests │ └── main.go │ └── wercker.yml ├── LICENSE ├── README.md ├── VERSION ├── api ├── registry.go ├── registry_test.go ├── remote.go └── remote_test.go ├── bin └── deploy_qa.sh ├── build.sh ├── build_qa.sh ├── circle.yml ├── main.go ├── main_test.go └── server ├── router.go └── router_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea/ 27 | .DS_Store 28 | node_modules/ 29 | 30 | imagelayers 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | 3 | MAINTAINER Ross Fairbanks 4 | 5 | ENV BUILD_PACKAGES ca-certificates 6 | 7 | RUN apk update && \ 8 | apk upgrade && \ 9 | apk add $BUILD_PACKAGES && \ 10 | rm -rf /var/cache/apk/* 11 | 12 | EXPOSE 8888 13 | COPY imagelayers Dockerfile / 14 | 15 | # Metadata params 16 | ARG BUILD_DATE 17 | ARG VERSION 18 | ARG VCS_REF 19 | 20 | # Metadata 21 | LABEL org.label-schema.url="https://imagelayers.io" \ 22 | org.label-schema.build-date=$BUILD_DATE \ 23 | org.label-schema.version=$VERSION \ 24 | org.label-schema.vcs-url="https://github.com/microscaling/imagelayers.git" \ 25 | org.label-schema.vcs-ref=$VCS_REF \ 26 | org.label-schema.docker.dockerfile="/Dockerfile" \ 27 | org.label-schema.description="This utility provides a browser-based visualization of user-specified Docker Images and their layers." \ 28 | org.label-schema.schema-version="1.0" 29 | 30 | ENTRYPOINT ["/imagelayers"] 31 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/microscaling/imagelayers", 3 | "GoVersion": "go1.4.2", 4 | "Packages": [ 5 | "./..." 6 | ], 7 | "Deps": [ 8 | { 9 | "ImportPath": "github.com/CenturyLinkLabs/docker-reg-client/registry", 10 | "Rev": "0bb0ba20bf1a05629226c2da0fdf7a37b9f661ba" 11 | }, 12 | { 13 | "ImportPath": "github.com/gorilla/context", 14 | "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd" 15 | }, 16 | { 17 | "ImportPath": "github.com/gorilla/mux", 18 | "Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf" 19 | }, 20 | { 21 | "ImportPath": "github.com/pmylund/go-cache", 22 | "Rev": "93d85800f2fa6bd0a739e7bd612bfa3bc008b72d" 23 | }, 24 | { 25 | "ImportPath": "github.com/rs/cors", 26 | "Rev": "6e0c3cb65fc0fdb064c743d176a620e3ca446dfb" 27 | }, 28 | { 29 | "ImportPath": "github.com/stretchr/objx", 30 | "Rev": "cbeaeb16a013161a98496fad62933b1d21786672" 31 | }, 32 | { 33 | "ImportPath": "github.com/stretchr/testify/assert", 34 | "Rev": "efa3c4c36479edac5af79cbcb4bc8cb525e09b13" 35 | }, 36 | { 37 | "ImportPath": "github.com/stretchr/testify/mock", 38 | "Rev": "efa3c4c36479edac5af79cbcb4bc8cb525e09b13" 39 | }, 40 | { 41 | "ImportPath": "golang.org/x/net/netutil", 42 | "Rev": "e0403b4e005737430c05a57aac078479844f919c" 43 | }, 44 | { 45 | "ImportPath": "gopkg.in/tylerb/graceful.v1", 46 | "Comment": "v1", 47 | "Rev": "d506b859c378b0e4a1b16d44021f3937f9ba4578" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/auth.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // Authenticator is the interface used to abstract various HTTP authentication 9 | // methods. 10 | // 11 | // ApplyAuthentication accepts an http.Request and will apply the appropriate 12 | // authentication headers to the request. 13 | type Authenticator interface { 14 | ApplyAuthentication(*http.Request) 15 | } 16 | 17 | // NilAuth is an Authenticator implementation that can be used when no 18 | // authentication is required. 19 | type NilAuth struct{} 20 | 21 | // No-op implementation. Makes NO changes to the specified request. 22 | func (NilAuth) ApplyAuthentication(r *http.Request) {} 23 | 24 | // BasicAuth is an Authenticator implementation which can be used for HTTP 25 | // basic authentication. 26 | type BasicAuth struct { 27 | Username string 28 | Password string 29 | } 30 | 31 | // Sets the Authorization header on the request to use HTTP Basic 32 | // Authentication with the username and password provided. 33 | func (a BasicAuth) ApplyAuthentication(r *http.Request) { 34 | r.SetBasicAuth(a.Username, a.Password) 35 | } 36 | 37 | // Access is an enum type representing the access type provided by a TokenAuth 38 | // authenticator. 39 | type Access int 40 | 41 | // Enum values for the Access type: None, Read, Write, Delete 42 | const ( 43 | None Access = iota 44 | Read 45 | Write 46 | Delete 47 | ) 48 | 49 | // TokenAuth is an Authenticator implementation which implements the token 50 | // authentication scheme used by the Docker Hub. 51 | type TokenAuth struct { 52 | Host string 53 | Token string 54 | Access Access 55 | } 56 | 57 | // Sets the Authorization header on the request to use the provided token. 58 | // Will also re-write the host value of the request URL to use the value 59 | // provided in the TokenAuth. 60 | func (a TokenAuth) ApplyAuthentication(r *http.Request) { 61 | token := fmt.Sprintf("Token %s", a.Token) 62 | r.Header.Add("Authorization", token) 63 | 64 | r.URL.Host = a.Host 65 | r.Host = a.Host 66 | } 67 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/auth_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestBasicAuth(t *testing.T) { 11 | username := "foo" 12 | password := "bar" 13 | auth := BasicAuth{username, password} 14 | 15 | req, _ := http.NewRequest("GET", "http://foo.com/v1", nil) 16 | auth.ApplyAuthentication(req) 17 | 18 | u, p, _ := req.BasicAuth() 19 | assert.Equal(t, username, u) 20 | assert.Equal(t, password, p) 21 | } 22 | 23 | func TestTokenAuth(t *testing.T) { 24 | host := "bar.com" 25 | token := "token xyz" 26 | auth := TokenAuth{Host: host, Token: token} 27 | 28 | req, _ := http.NewRequest("GET", "http://foo.com/v1", nil) 29 | auth.ApplyAuthentication(req) 30 | 31 | assert.Equal(t, "Token "+token, req.Header.Get("Authorization")) 32 | assert.Equal(t, host, req.Host) 33 | assert.Equal(t, host, req.URL.Host) 34 | } 35 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package registry is an API wrapper for the Docker Registry v1 API and supports 3 | all of the documented operations, including: getting/putting images, retrieving 4 | repository tags, and executing searches. 5 | 6 | Full documentation for the Docker Registry API can be found here: 7 | 8 | https://docs.docker.com/reference/api/registry_api/ 9 | 10 | Usage 11 | 12 | There are two different usage models for the registry client depending on 13 | whether you are trying to interact with a private Docker registry or the Docker 14 | Hub. 15 | 16 | When using the client with a private Docker registry you can simply pass basic 17 | auth credentials to the functions you are invoking and the appropriate 18 | authentication headers will be passed along with the request: 19 | 20 | c := registry.NewClient() 21 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 22 | 23 | auth := registry.BasicAuth{"user", "pass"} 24 | 25 | err := c.Repository.SetTag("foo/bar", "abc123", "1.0", auth) 26 | 27 | When using the client to interact with the Docker Hub there is an extra call 28 | that you need to make. You need to use your basic auth credentials to first 29 | retrieve a token and then you can use that token when interacting with the 30 | Registry API: 31 | 32 | c := registry.NewClient() 33 | 34 | basicAuth := registry.BasicAuth{"user", "pass"} 35 | 36 | tokenAuth, err := c.Hub.GetWriteToken("foo/bar", basicAuth) 37 | 38 | err := c.Repository.SetTag("foo/bar", "abc123", "1.0", tokenAuth) 39 | 40 | Depending on the API you're trying to use, you may need to first retreive either 41 | a read, write or delete token. 42 | 43 | For more details about interacting with the Docker Hub, see the Docker Hub and 44 | Registry Spec: 45 | 46 | https://docs.docker.com/reference/api/hub_registry_spec/ 47 | 48 | Note: If your Docker Hub repository has been setup as an Automated Build 49 | repositry you will not be able to make any changes to it (setting tags, etc...) 50 | via the Registry API. You can only make changes to an Automated Build 51 | repository through the Docker Hub UI. 52 | */ 53 | package registry 54 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/example_test.go: -------------------------------------------------------------------------------- 1 | package registry_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/CenturyLinkLabs/docker-reg-client/registry" 9 | ) 10 | 11 | func ExampleRepositoryService_ListTags_dockerHub() { 12 | c := registry.NewClient() 13 | auth, err := c.Hub.GetReadToken("ubuntu") 14 | 15 | tags, err := c.Repository.ListTags("ubuntu", auth) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | for tag, imageID := range tags { 21 | fmt.Printf("%s = %s", tag, imageID) 22 | } 23 | } 24 | 25 | func ExampleRepositoryService_ListTags_privateRegistry() { 26 | c := registry.NewClient() 27 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 28 | auth := registry.BasicAuth{"user", "pass"} 29 | 30 | tags, err := c.Repository.ListTags("ubuntu", auth) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | for tag, imageID := range tags { 36 | fmt.Printf("%s = %s", tag, imageID) 37 | } 38 | } 39 | 40 | func ExampleRepositoryService_DeleteTag_privateRegistry() { 41 | c := registry.NewClient() 42 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 43 | auth := registry.BasicAuth{"user", "pass"} 44 | 45 | err := c.Repository.DeleteTag("foo/bar", "1.0", auth) 46 | if err != nil { 47 | panic(err) 48 | } 49 | } 50 | 51 | func ExampleRepositoryService_GetImageID_dockerHub() { 52 | c := registry.NewClient() 53 | auth, err := c.Hub.GetReadToken("ubuntu") 54 | 55 | imageID, err := c.Repository.GetImageID("ubuntu", "latest", auth) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | fmt.Println(imageID) 61 | } 62 | 63 | func ExampleRepositoryService_GetImageID_privateRegistry() { 64 | c := registry.NewClient() 65 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 66 | auth := registry.BasicAuth{"user", "pass"} 67 | 68 | imageID, err := c.Repository.GetImageID("ubuntu", "latest", auth) 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | fmt.Println(imageID) 74 | } 75 | 76 | func ExampleRepositoryService_Delete_dockerHub() { 77 | c := registry.NewClient() 78 | basicAuth := registry.BasicAuth{"user", "pass"} 79 | tokenAuth, err := c.Hub.GetDeleteToken("foo/bar", basicAuth) 80 | 81 | err = c.Repository.Delete("foo/bar", tokenAuth) 82 | if err != nil { 83 | panic(err) 84 | } 85 | } 86 | 87 | func ExampleRepositoryService_Delete_privateRegistry() { 88 | c := registry.NewClient() 89 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 90 | auth := registry.BasicAuth{"user", "pass"} 91 | 92 | err := c.Repository.Delete("foo/bar", auth) 93 | if err != nil { 94 | panic(err) 95 | } 96 | } 97 | 98 | func ExampleRepositoryService_SetTag_dockerHub() { 99 | c := registry.NewClient() 100 | basicAuth := registry.BasicAuth{"user", "pass"} 101 | tokenAuth, err := c.Hub.GetWriteToken("foo/bar", basicAuth) 102 | 103 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 104 | err = c.Repository.SetTag("foo/bar", imageID, "1.0", tokenAuth) 105 | if err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | func ExampleRepositoryService_SetTag_privateRegistry() { 111 | c := registry.NewClient() 112 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 113 | auth := registry.BasicAuth{"user", "pass"} 114 | 115 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 116 | err := c.Repository.SetTag("foo/bar", imageID, "1.0", auth) 117 | if err != nil { 118 | panic(err) 119 | } 120 | } 121 | 122 | func ExampleImageService_GetAncestry_dockerHub() { 123 | c := registry.NewClient() 124 | auth, err := c.Hub.GetReadToken("ubuntu") 125 | 126 | images, err := c.Image.GetAncestry("ubuntu", auth) 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | for _, imageID := range images { 132 | fmt.Println(imageID) 133 | } 134 | } 135 | 136 | func ExampleImageService_GetAncestry_privateRegistry() { 137 | c := registry.NewClient() 138 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 139 | auth := registry.BasicAuth{"user", "pass"} 140 | 141 | images, err := c.Image.GetAncestry("ubuntu", auth) 142 | if err != nil { 143 | panic(err) 144 | } 145 | 146 | for _, imageID := range images { 147 | fmt.Println(imageID) 148 | } 149 | } 150 | 151 | func ExampleImageService_GetMetadata_dockerHub() { 152 | c := registry.NewClient() 153 | auth, err := c.Hub.GetReadToken("ubuntu") 154 | 155 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 156 | meta, err := c.Image.GetMetadata(imageID, auth) 157 | if err != nil { 158 | panic(err) 159 | } 160 | 161 | fmt.Printf("%#v", meta) 162 | } 163 | 164 | func ExampleImageService_GetMetadata_privateRegistry() { 165 | c := registry.NewClient() 166 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 167 | auth := registry.BasicAuth{"user", "pass"} 168 | 169 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 170 | meta, err := c.Image.GetMetadata(imageID, auth) 171 | if err != nil { 172 | panic(err) 173 | } 174 | 175 | fmt.Printf("%#v", meta) 176 | } 177 | 178 | func ExampleImageService_GetLayer_dockerHub() { 179 | c := registry.NewClient() 180 | auth, err := c.Hub.GetReadToken("ubuntu") 181 | 182 | buffer := &bytes.Buffer{} 183 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 184 | err = c.Image.GetLayer(imageID, buffer, auth) 185 | if err != nil { 186 | panic(err) 187 | } 188 | 189 | fmt.Println(buffer) 190 | } 191 | 192 | func ExampleImageService_GetLayer_privateRegistry() { 193 | c := registry.NewClient() 194 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 195 | auth := registry.BasicAuth{"user", "pass"} 196 | 197 | buffer := &bytes.Buffer{} 198 | imageID := "2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1" 199 | err := c.Image.GetLayer(imageID, buffer, auth) 200 | if err != nil { 201 | panic(err) 202 | } 203 | 204 | fmt.Println(buffer) 205 | } 206 | 207 | func ExampleSearchService_Query_dockerHub() { 208 | c := registry.NewClient() 209 | 210 | results, err := c.Search.Query("mysql", 1, 25) 211 | if err != nil { 212 | panic(err) 213 | } 214 | 215 | for _, result := range results.Results { 216 | fmt.Println(result.Name) 217 | } 218 | } 219 | 220 | func ExampleSearchService_Query_privateRegistry() { 221 | c := registry.NewClient() 222 | c.BaseURL, _ = url.Parse("http://my.registry:8080/v1/") 223 | 224 | results, err := c.Search.Query("mysql", 1, 25) 225 | if err != nil { 226 | panic(err) 227 | } 228 | 229 | for _, result := range results.Results { 230 | fmt.Println(result.Name) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/hub.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | ) 8 | 9 | // HubService gives acess to the Docker Hub API used to retrieve authentication tokens. 10 | type HubService struct { 11 | client *Client 12 | } 13 | 14 | // Retrieves a read-only token from the Docker Hub for the specified repo. 15 | func (h *HubService) GetReadToken(repo string) (*TokenAuth, error) { 16 | path := fmt.Sprintf("repositories/%s/images", repo) 17 | req, err := h.newRequest("GET", path, NilAuth{}) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return h.do(req) 23 | } 24 | 25 | // Retrieves a read-only token from the Docker Hub for the specified private repo. 26 | func (h *HubService) GetReadTokenWithAuth(repo string, auth Authenticator) (*TokenAuth, error) { 27 | path := fmt.Sprintf("repositories/%s/images", repo) 28 | req, err := h.newRequest("GET", path, auth) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return h.do(req) 34 | } 35 | 36 | // Retrieves a write-only token from the Docker Hub for the specified repo. The 37 | // auth argument should be the user's basic auth credentials. 38 | func (h *HubService) GetWriteToken(repo string, auth Authenticator) (*TokenAuth, error) { 39 | path := fmt.Sprintf("repositories/%s/", repo) 40 | req, err := h.newRequest("PUT", path, auth) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return h.do(req) 46 | } 47 | 48 | // Retrieves a write-only token from the Docker Hub for the specified repo. The 49 | // auth argument should be the user's basic auth credentials. 50 | func (h *HubService) GetDeleteToken(repo string, auth Authenticator) (*TokenAuth, error) { 51 | path := fmt.Sprintf("repositories/%s/", repo) 52 | req, err := h.newRequest("DELETE", path, auth) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return h.do(req) 58 | } 59 | 60 | func (h *HubService) newRequest(method, urlStr string, auth Authenticator) (*http.Request, error) { 61 | req, err := h.client.newRequest(method, urlStr, auth, []string{}) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | req.Header.Add("X-Docker-Token", "true") 67 | 68 | return req, nil 69 | } 70 | 71 | func (h *HubService) do(req *http.Request) (*TokenAuth, error) { 72 | res, err := h.client.do(req, nil) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | token := &TokenAuth{Token: res.Header.Get("X-Docker-Token")} 78 | 79 | switch req.Method { 80 | case "GET": 81 | token.Access = Read 82 | case "PUT": 83 | token.Access = Write 84 | case "DELETE": 85 | token.Access = Delete 86 | } 87 | 88 | // Need to parse the value cause different requests return the endpoints 89 | // in different formats 90 | url, _ := url.Parse(res.Header.Get("X-Docker-Endpoints")) 91 | if url.Host != "" { 92 | token.Host = url.Host 93 | } else { 94 | token.Host = url.Path 95 | } 96 | 97 | return token, nil 98 | } 99 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/hub_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestHubGetReadToken(t *testing.T) { 11 | setup() 12 | defer teardown() 13 | 14 | token := "foo123" 15 | host := "www.bar.com" 16 | 17 | mux.HandleFunc("/repositories/foo/images", func(w http.ResponseWriter, r *http.Request) { 18 | assert.Equal(t, "GET", r.Method) 19 | assert.Equal(t, "true", r.Header.Get("X-Docker-Token")) 20 | 21 | w.Header().Add("X-Docker-Token", token) 22 | w.Header().Add("X-Docker-Endpoints", host) 23 | }) 24 | 25 | auth, err := client.Hub.GetReadToken("foo") 26 | 27 | assert.NoError(t, err) 28 | assert.Equal(t, Read, auth.Access) 29 | assert.Equal(t, token, auth.Token) 30 | assert.Equal(t, host, auth.Host) 31 | } 32 | 33 | func TestHubGetReadToken_Error(t *testing.T) { 34 | setup() 35 | defer teardown() 36 | 37 | mux.HandleFunc("/repositories/foo/images", func(w http.ResponseWriter, r *http.Request) { 38 | assert.Equal(t, "GET", r.Method) 39 | assert.Equal(t, "true", r.Header.Get("X-Docker-Token")) 40 | 41 | w.WriteHeader(http.StatusNotFound) 42 | }) 43 | 44 | _, err := client.Hub.GetReadToken("foo") 45 | 46 | assert.Contains(t, err.Error(), "/repositories/foo/images returned 404") 47 | assert.IsType(t, RegistryError{}, err) 48 | re, _ := err.(RegistryError) 49 | assert.Equal(t, 404, re.Code) 50 | } 51 | 52 | func TestHubGetWriteToken(t *testing.T) { 53 | setup() 54 | defer teardown() 55 | 56 | token := "foo123" 57 | host := "www.bar.com" 58 | 59 | mux.HandleFunc("/repositories/foo/", func(w http.ResponseWriter, r *http.Request) { 60 | assert.Equal(t, "PUT", r.Method) 61 | assert.Equal(t, "true", r.Header.Get("X-Docker-Token")) 62 | 63 | w.Header().Add("X-Docker-Token", token) 64 | w.Header().Add("X-Docker-Endpoints", host) 65 | }) 66 | 67 | auth, err := client.Hub.GetWriteToken("foo", NilAuth{}) 68 | 69 | assert.NoError(t, err) 70 | assert.Equal(t, Write, auth.Access) 71 | assert.Equal(t, token, auth.Token) 72 | assert.Equal(t, host, auth.Host) 73 | } 74 | 75 | func TestHubGetDeleteToken(t *testing.T) { 76 | setup() 77 | defer teardown() 78 | 79 | token := "foo123" 80 | host := "www.bar.com" 81 | 82 | mux.HandleFunc("/repositories/foo/", func(w http.ResponseWriter, r *http.Request) { 83 | assert.Equal(t, "DELETE", r.Method) 84 | assert.Equal(t, "true", r.Header.Get("X-Docker-Token")) 85 | 86 | w.Header().Add("X-Docker-Token", token) 87 | w.Header().Add("X-Docker-Endpoints", host) 88 | }) 89 | 90 | auth, err := client.Hub.GetDeleteToken("foo", NilAuth{}) 91 | 92 | assert.NoError(t, err) 93 | assert.Equal(t, Delete, auth.Access) 94 | assert.Equal(t, token, auth.Token) 95 | assert.Equal(t, host, auth.Host) 96 | } 97 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/image.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "time" 7 | ) 8 | 9 | // ImageService gives access to the /images portion of the Registry API. 10 | type ImageService struct { 11 | client *Client 12 | } 13 | 14 | // ImageMetadata represents metadata about an image layer. 15 | type ImageMetadata struct { 16 | ID string `json:"id"` 17 | Parent string `json:"parent"` 18 | Comment string `json:"Comment"` 19 | Created time.Time `json:"created"` 20 | Container string `json:"container"` 21 | ContainerConfig ContainerConfig `json:"container_config"` 22 | DockerVersion string `json:"docker_version"` 23 | Author string `json:"author"` 24 | Config *ContainerConfig `json:"config"` 25 | Architecture string `json:"architecture"` 26 | OS string `json:"os"` 27 | Size int64 `json:"Size"` 28 | } 29 | 30 | // ContainerConfig is the list of configuration options used when creating a container. 31 | type ContainerConfig struct { 32 | HostName string `json:"Hostname"` 33 | DomainName string `json:"Domainname"` 34 | User string `json:"User"` 35 | Memory int64 `json:"Memory"` 36 | MemorySwap int64 `json:"MemorySwap"` 37 | CPUShares int64 `json:"CpuShares"` 38 | CPUSet string `json:"Cpuset"` 39 | AttachStdin bool `json:"AttachStdin"` 40 | AttachStdout bool `json:"AttachStdout"` 41 | AttachStderr bool `json:"AttachStderr"` 42 | PortSpecs []string `json:"PortSpecs"` 43 | ExposedPorts map[string]struct{} `json:"ExposedPorts"` 44 | TTY bool `json:"Tty"` 45 | OpenStdin bool `json:"OpenStdin"` 46 | StdinOnce bool `json:"StdinOnce"` 47 | Env []string `json:"Env"` 48 | Cmd []string `json:"Cmd"` 49 | DNS []string `json:"Dns"` 50 | Image string `json:"Image"` 51 | Volumes map[string]struct{} `json:"Volumes"` 52 | VolumesFrom string `json:"VolumesFrom"` 53 | WorkingDir string `json:"WorkingDir"` 54 | Entrypoint []string `json:"Entrypoint"` 55 | NetworkDisabled bool `json:"NetworkDisabled"` 56 | SecurityOpts []string `json:"SecurityOpts"` 57 | OnBuild []string `json:"OnBuild"` 58 | } 59 | 60 | // Retrieve the layer for a given image ID. Contents of the layer will be 61 | // written to the provided io.Writer. 62 | // 63 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#get-image-layer 64 | func (i *ImageService) GetLayer(imageID string, layer io.Writer, auth Authenticator) error { 65 | path := fmt.Sprintf("images/%s/layer", imageID) 66 | req, err := i.client.newRequest("GET", path, auth, layer) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | _, err = i.client.do(req, layer) 72 | return err 73 | } 74 | 75 | // Upload the layer for a given image ID. Contents of the layer will be read 76 | // from the provided io.Reader. 77 | // 78 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#put-image-layer 79 | func (i *ImageService) AddLayer(imageID string, layer io.Reader, auth Authenticator) error { 80 | path := fmt.Sprintf("images/%s/layer", imageID) 81 | req, err := i.client.newRequest("PUT", path, auth, layer) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | _, err = i.client.do(req, nil) 87 | return err 88 | } 89 | 90 | // Retrieve the layer metadata for a given image ID. 91 | // 92 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#get-image-layer_1 93 | func (i *ImageService) GetMetadata(imageID string, auth Authenticator) (*ImageMetadata, error) { 94 | path := fmt.Sprintf("images/%s/json", imageID) 95 | req, err := i.client.newRequest("GET", path, auth, nil) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | meta := &ImageMetadata{} 101 | _, err = i.client.do(req, meta) 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | return meta, nil 107 | } 108 | 109 | // Upload the layer metadata for a given image ID. 110 | // 111 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#put-image-layer_1 112 | func (i *ImageService) AddMetadata(imageID string, metadata *ImageMetadata, auth Authenticator) error { 113 | path := fmt.Sprintf("images/%s/json", imageID) 114 | req, err := i.client.newRequest("PUT", path, auth, metadata) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | req.Header.Add("Content-Type", "application/json") 120 | 121 | _, err = i.client.do(req, nil) 122 | return err 123 | } 124 | 125 | // Retrieve the ancestry for an image given an image ID. 126 | // 127 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#get-image-ancestry 128 | func (i *ImageService) GetAncestry(imageID string, auth Authenticator) ([]string, error) { 129 | path := fmt.Sprintf("images/%s/ancestry", imageID) 130 | req, err := i.client.newRequest("GET", path, auth, nil) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | var ancestry []string 136 | _, err = i.client.do(req, &ancestry) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | return ancestry, nil 142 | } 143 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/image_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestImageGetLayer(t *testing.T) { 16 | setup() 17 | defer teardown() 18 | 19 | expected := "_foo_\n_bar_" 20 | 21 | mux.HandleFunc("/images/1234567890/layer", func(w http.ResponseWriter, r *http.Request) { 22 | assert.Equal(t, "GET", r.Method) 23 | fmt.Fprint(w, expected) 24 | }) 25 | 26 | buffer := &bytes.Buffer{} 27 | err := client.Image.GetLayer("1234567890", buffer, NilAuth{}) 28 | 29 | assert.NoError(t, err) 30 | assert.Equal(t, expected, buffer.String()) 31 | } 32 | 33 | func TestImageAddLayer(t *testing.T) { 34 | setup() 35 | defer teardown() 36 | 37 | expected := "_foo_\n_bar_" 38 | 39 | mux.HandleFunc("/images/1234567890/layer", func(w http.ResponseWriter, r *http.Request) { 40 | assert.Equal(t, "PUT", r.Method) 41 | 42 | body, _ := ioutil.ReadAll(r.Body) 43 | assert.Equal(t, expected, string(body)) 44 | }) 45 | 46 | buffer := bytes.NewBufferString(expected) 47 | err := client.Image.AddLayer("1234567890", buffer, NilAuth{}) 48 | 49 | assert.NoError(t, err) 50 | } 51 | 52 | func TestImageGetMetadata(t *testing.T) { 53 | setup() 54 | defer teardown() 55 | 56 | loc, _ := time.LoadLocation("") 57 | expected := &ImageMetadata{ 58 | ID: "1234567890", 59 | Parent: "0987654321", 60 | Comment: "lgtm", 61 | Created: time.Date(1973, 10, 25, 8, 0, 0, 0, loc), 62 | Author: "Brian DeHamer", 63 | Size: 20909309, 64 | } 65 | 66 | mux.HandleFunc("/images/1234567890/json", func(w http.ResponseWriter, r *http.Request) { 67 | assert.Equal(t, "GET", r.Method) 68 | json.NewEncoder(w).Encode(expected) 69 | }) 70 | 71 | meta, err := client.Image.GetMetadata("1234567890", NilAuth{}) 72 | 73 | assert.NoError(t, err) 74 | assert.Equal(t, expected, meta) 75 | } 76 | 77 | func TestImageAddMetadata(t *testing.T) { 78 | setup() 79 | defer teardown() 80 | 81 | meta := &ImageMetadata{ 82 | ID: "1234567890", 83 | Parent: "0987654321", 84 | Comment: "lgtm", 85 | Created: time.Now(), 86 | Author: "Brian DeHamer", 87 | Size: 20909309, 88 | } 89 | 90 | mux.HandleFunc("/images/1234567890/json", func(w http.ResponseWriter, r *http.Request) { 91 | assert.Equal(t, "PUT", r.Method) 92 | assert.Equal(t, "application/json", r.Header.Get("Content-Type")) 93 | 94 | body, _ := ioutil.ReadAll(r.Body) 95 | 96 | expected, _ := json.Marshal(meta) 97 | expected = append(expected, 0xa) 98 | assert.Equal(t, expected, body) 99 | }) 100 | 101 | err := client.Image.AddMetadata("1234567890", meta, NilAuth{}) 102 | 103 | assert.NoError(t, err) 104 | } 105 | 106 | func TestImageGetAncestry(t *testing.T) { 107 | setup() 108 | defer teardown() 109 | 110 | expected := []string{"12345", "67890"} 111 | 112 | mux.HandleFunc("/images/1234567890/ancestry", func(w http.ResponseWriter, r *http.Request) { 113 | assert.Equal(t, "GET", r.Method) 114 | json.NewEncoder(w).Encode(expected) 115 | }) 116 | 117 | ancestry, err := client.Image.GetAncestry("1234567890", NilAuth{}) 118 | 119 | assert.NoError(t, err) 120 | assert.Equal(t, expected, ancestry) 121 | } 122 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | const ( 13 | defaultBaseURL = "https://index.docker.io/v1/" 14 | ) 15 | 16 | // Client manages communication with the Docker Registry API. 17 | type Client struct { 18 | // BaseURL represents the base URL for the Registry API. 19 | // Defaults to "https://index.docker.io/v1/". 20 | BaseURL *url.URL 21 | 22 | // Hub gives access to the Docker Hub API for retrieving auth tokens. 23 | Hub *HubService 24 | 25 | // Image gives access to the /images part of the Registry API. 26 | Image *ImageService 27 | 28 | // Repository gives access to the /repositories part of the Registry API. 29 | Repository *RepositoryService 30 | 31 | // Search gives access to the /search part of the Registry API. 32 | Search *SearchService 33 | 34 | client *http.Client 35 | } 36 | 37 | // RegistryError encapsulates any errors which result from communicating with 38 | // the Docker Registry API 39 | type RegistryError struct { 40 | Code int 41 | method string 42 | url *url.URL 43 | } 44 | 45 | func (e RegistryError) Error() string { 46 | return fmt.Sprintf("%s %s returned %d", e.method, e.url, e.Code) 47 | } 48 | 49 | // NewClient returns a new Docker Registry API client. 50 | func NewClient() *Client { 51 | baseURL, err := url.Parse(defaultBaseURL) 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | c := &Client{ 57 | BaseURL: baseURL, 58 | client: http.DefaultClient, 59 | } 60 | 61 | c.Hub = &HubService{client: c} 62 | c.Image = &ImageService{client: c} 63 | c.Repository = &RepositoryService{client: c} 64 | c.Search = &SearchService{client: c} 65 | 66 | return c 67 | } 68 | 69 | // Creates a new API request. Relative URLs should be specified without a 70 | // preceding slash. 71 | func (c *Client) newRequest(method, urlStr string, auth Authenticator, body interface{}) (*http.Request, error) { 72 | rel, err := url.Parse(urlStr) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | url := c.BaseURL.ResolveReference(rel) 78 | 79 | buf := &bytes.Buffer{} 80 | if body != nil { 81 | if reader, ok := body.(io.Reader); ok { 82 | io.Copy(buf, reader) 83 | } else { 84 | err = json.NewEncoder(buf).Encode(body) 85 | if err != nil { 86 | return nil, err 87 | } 88 | } 89 | } 90 | 91 | req, err := http.NewRequest(method, url.String(), buf) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | auth.ApplyAuthentication(req) 97 | 98 | return req, nil 99 | } 100 | 101 | // Performs the given request and decodes the response into the variable v. 102 | func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) { 103 | resp, err := c.client.Do(req) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | defer resp.Body.Close() 109 | 110 | if status := resp.StatusCode; status < 200 || status > 299 { 111 | return resp, RegistryError{Code: status, method: req.Method, url: req.URL} 112 | } 113 | 114 | if v != nil { 115 | if writer, ok := v.(io.Writer); ok { 116 | io.Copy(writer, resp.Body) 117 | } else { 118 | err = json.NewDecoder(resp.Body).Decode(v) 119 | } 120 | } 121 | 122 | return resp, err 123 | } 124 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/registry_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/http/httptest" 10 | "net/url" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | var ( 17 | mux *http.ServeMux 18 | server *httptest.Server 19 | client *Client 20 | ) 21 | 22 | func setup() { 23 | mux = http.NewServeMux() 24 | server = httptest.NewServer(mux) 25 | 26 | client = NewClient() 27 | url, _ := url.Parse(server.URL) 28 | client.BaseURL = url 29 | } 30 | 31 | func teardown() { 32 | server.Close() 33 | } 34 | 35 | type mockAuth struct{} 36 | 37 | func (mockAuth) ApplyAuthentication(r *http.Request) { 38 | r.Header.Add("Authentication", "applied") 39 | } 40 | 41 | // Test Client.newRequest when supplying JSON-serializable object for request body 42 | func TestNewRequestJSON(t *testing.T) { 43 | c := NewClient() 44 | 45 | expectedMethod := "GET" 46 | inURL, expectedURL := "foo", defaultBaseURL+"foo" 47 | inBody, expectedBody := map[string]string{"foo": "bar"}, "{\"foo\":\"bar\"}\n" 48 | 49 | r, err := c.newRequest(expectedMethod, inURL, mockAuth{}, inBody) 50 | 51 | body, _ := ioutil.ReadAll(r.Body) 52 | assert.NoError(t, err) 53 | assert.Equal(t, expectedMethod, r.Method) 54 | assert.Equal(t, expectedURL, r.URL.String()) 55 | assert.Equal(t, expectedBody, string(body)) 56 | assert.Equal(t, "applied", r.Header.Get("Authentication"), "Authentication not applied") 57 | } 58 | 59 | // Test Client.newRequest when supplying io.Reader for request body 60 | func TestNewRequestReader(t *testing.T) { 61 | c := NewClient() 62 | 63 | expectedMethod := "GET" 64 | inURL, expectedURL := "foo", defaultBaseURL+"foo" 65 | inBody, expectedBody := bytes.NewBufferString("foobar"), "foobar" 66 | 67 | r, err := c.newRequest(expectedMethod, inURL, mockAuth{}, inBody) 68 | 69 | body, _ := ioutil.ReadAll(r.Body) 70 | assert.NoError(t, err) 71 | assert.Equal(t, expectedMethod, r.Method) 72 | assert.Equal(t, expectedURL, r.URL.String()) 73 | assert.Equal(t, expectedBody, string(body)) 74 | assert.Equal(t, "applied", r.Header.Get("Authentication"), "Authentication not applied") 75 | } 76 | 77 | // Test Client.do when supplying an object for JSON deserialization of response body 78 | func TestDoJSON(t *testing.T) { 79 | setup() 80 | defer teardown() 81 | 82 | expected := map[string]string{"foo": "bar"} 83 | 84 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 85 | assert.Equal(t, "GET", r.Method) 86 | json.NewEncoder(w).Encode(expected) 87 | }) 88 | 89 | req, _ := client.newRequest("GET", "/", NilAuth{}, nil) 90 | body := map[string]string{} 91 | 92 | _, err := client.do(req, &body) 93 | 94 | assert.NoError(t, err) 95 | assert.Equal(t, expected, body) 96 | } 97 | 98 | // Test Client.do when supplying an io.Writer for receipt of response body 99 | func TestDoWriter(t *testing.T) { 100 | setup() 101 | defer teardown() 102 | 103 | str := "foo_bar" 104 | 105 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 106 | assert.Equal(t, "GET", r.Method) 107 | fmt.Fprint(w, str) 108 | }) 109 | 110 | req, _ := client.newRequest("GET", "/", NilAuth{}, nil) 111 | body := bytes.Buffer{} 112 | 113 | _, err := client.do(req, &body) 114 | 115 | assert.NoError(t, err) 116 | assert.Equal(t, str, body.String()) 117 | } 118 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/repository.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ImageService gives access to the /repositories portion of the Registry API. 8 | type RepositoryService struct { 9 | client *Client 10 | } 11 | 12 | // TagMap represents a collection of image tags and their corresponding image IDs. 13 | type TagMap map[string]string 14 | 15 | // Retrieves a list of tags for the specified repository. 16 | // 17 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#list-repository-tags 18 | func (r *RepositoryService) ListTags(repo string, auth Authenticator) (TagMap, error) { 19 | path := fmt.Sprintf("repositories/%s/tags", repo) 20 | req, err := r.client.newRequest("GET", path, auth, nil) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | tags := TagMap{} 26 | _, err = r.client.do(req, &tags) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | return tags, nil 32 | } 33 | 34 | // Retrieves the ID of the image identified by the given repository and tag. 35 | // 36 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#get-image-id-for-a-particular-tag 37 | func (r *RepositoryService) GetImageID(repo, tag string, auth Authenticator) (string, error) { 38 | path := fmt.Sprintf("repositories/%s/tags/%s", repo, tag) 39 | req, err := r.client.newRequest("GET", path, auth, nil) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | var id string 45 | _, err = r.client.do(req, &id) 46 | if err != nil { 47 | return "", err 48 | } 49 | 50 | return id, nil 51 | } 52 | 53 | // Sets a tag for the the specified repository and image ID. 54 | // 55 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#set-a-tag-for-a-specified-image-id 56 | func (r *RepositoryService) SetTag(repo, imageID, tag string, auth Authenticator) error { 57 | path := fmt.Sprintf("repositories/%s/tags/%s", repo, tag) 58 | req, err := r.client.newRequest("PUT", path, auth, imageID) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | _, err = r.client.do(req, nil) 64 | return err 65 | } 66 | 67 | // Deletes a tag from the specified repository. 68 | // 69 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#delete-a-repository-tag 70 | func (r *RepositoryService) DeleteTag(repo, tag string, auth Authenticator) error { 71 | path := fmt.Sprintf("repositories/%s/tags/%s", repo, tag) 72 | req, err := r.client.newRequest("DELETE", path, auth, nil) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | _, err = r.client.do(req, nil) 78 | return err 79 | } 80 | 81 | // Deletes the specified repository. 82 | // 83 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#delete-a-repository 84 | func (r *RepositoryService) Delete(repo string, auth Authenticator) error { 85 | path := fmt.Sprintf("repositories/%s/", repo) 86 | req, err := r.client.newRequest("DELETE", path, auth, nil) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | _, err = r.client.do(req, nil) 92 | return err 93 | } 94 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/repository_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRepositoryListTags(t *testing.T) { 13 | setup() 14 | defer teardown() 15 | 16 | expected := TagMap{"bar": "1234567890"} 17 | 18 | mux.HandleFunc("/repositories/foo/tags", func(w http.ResponseWriter, r *http.Request) { 19 | assert.Equal(t, "GET", r.Method) 20 | json.NewEncoder(w).Encode(expected) 21 | }) 22 | 23 | tags, err := client.Repository.ListTags("foo", NilAuth{}) 24 | 25 | assert.NoError(t, err) 26 | assert.Equal(t, expected, tags) 27 | } 28 | 29 | func TestRepositoryGetImageID(t *testing.T) { 30 | setup() 31 | defer teardown() 32 | 33 | expected := "1234567890" 34 | 35 | mux.HandleFunc("/repositories/foo/tags/bar", func(w http.ResponseWriter, r *http.Request) { 36 | assert.Equal(t, "GET", r.Method) 37 | json.NewEncoder(w).Encode(expected) 38 | }) 39 | 40 | imageID, err := client.Repository.GetImageID("foo", "bar", NilAuth{}) 41 | 42 | assert.NoError(t, err) 43 | assert.Equal(t, expected, imageID) 44 | } 45 | 46 | func TestRepositorySetTag(t *testing.T) { 47 | setup() 48 | defer teardown() 49 | 50 | mux.HandleFunc("/repositories/foo/tags/bar", func(w http.ResponseWriter, r *http.Request) { 51 | assert.Equal(t, "PUT", r.Method) 52 | 53 | body, _ := ioutil.ReadAll(r.Body) 54 | assert.Equal(t, "\"1234567890\"\n", string(body)) 55 | }) 56 | 57 | err := client.Repository.SetTag("foo", "1234567890", "bar", NilAuth{}) 58 | 59 | assert.NoError(t, err) 60 | } 61 | 62 | func TestRepositoryDeleteTag(t *testing.T) { 63 | setup() 64 | defer teardown() 65 | 66 | mux.HandleFunc("/repositories/foo/tags/bar", func(w http.ResponseWriter, r *http.Request) { 67 | assert.Equal(t, "DELETE", r.Method) 68 | }) 69 | 70 | err := client.Repository.DeleteTag("foo", "bar", NilAuth{}) 71 | 72 | assert.NoError(t, err) 73 | } 74 | 75 | func TestRepositoryDelete(t *testing.T) { 76 | setup() 77 | defer teardown() 78 | 79 | mux.HandleFunc("/repositories/foo/", func(w http.ResponseWriter, r *http.Request) { 80 | assert.Equal(t, "DELETE", r.Method) 81 | }) 82 | 83 | err := client.Repository.Delete("foo", NilAuth{}) 84 | 85 | assert.NoError(t, err) 86 | } 87 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/search.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | // SearchService gives access to the /search portion of the Registry API 10 | type SearchService struct { 11 | client *Client 12 | } 13 | 14 | // SearchResults represents the list of SearchResult structs returned from a 15 | // search against the registry. 16 | type SearchResults struct { 17 | NumPages int `json:"num_pages"` 18 | NumResults int `json:"num_results"` 19 | Results []SearchResult `json:"results"` 20 | PageSize int `json:"page_size"` 21 | Query string `json:"query"` 22 | Page int `json:"page,string"` 23 | } 24 | 25 | // SearchResult represents a single result returned from a search against the 26 | // registry. 27 | type SearchResult struct { 28 | Name string `json:"name"` 29 | IsAutomated bool `json:"is_automated"` 30 | IsTrusted bool `json:"is_trusted"` 31 | IsOfficial bool `json:"is_official"` 32 | StarCount int `json:"star_count"` 33 | Description string `json:"description"` 34 | } 35 | 36 | // Searches the registry for the given term. The page argument is the page 37 | // number for the desired results. The num argument is the number of results 38 | // to be returned per page. 39 | // 40 | // Docker Registry API docs: https://docs.docker.com/reference/api/registry_api/#search 41 | func (s *SearchService) Query(query string, page, num int) (*SearchResults, error) { 42 | params := url.Values{} 43 | params.Add("q", query) 44 | params.Add("page", strconv.Itoa(page)) 45 | params.Add("n", strconv.Itoa(num)) 46 | path := fmt.Sprintf("search?%s", params.Encode()) 47 | 48 | req, err := s.client.newRequest("GET", path, NilAuth{}, nil) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | results := &SearchResults{} 54 | _, err = s.client.do(req, results) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return results, nil 60 | } 61 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/CenturyLinkLabs/docker-reg-client/registry/search_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSearchQuery(t *testing.T) { 12 | setup() 13 | defer teardown() 14 | 15 | expected := &SearchResults{ 16 | NumPages: 1, 17 | NumResults: 1, 18 | Results: []SearchResult{{Name: "foo"}}, 19 | PageSize: 1, 20 | Query: "foo", 21 | Page: 1, 22 | } 23 | 24 | mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) { 25 | assert.Equal(t, "GET", r.Method) 26 | assert.Equal(t, "foo", r.FormValue("q")) 27 | assert.Equal(t, "2", r.FormValue("page")) 28 | assert.Equal(t, "3", r.FormValue("n")) 29 | json.NewEncoder(w).Encode(expected) 30 | }) 31 | 32 | results, err := client.Search.Query("foo", 2, 3) 33 | 34 | assert.NoError(t, err) 35 | assert.Equal(t, expected, results) 36 | } 37 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - 1.2 7 | - 1.3 8 | - 1.4 9 | - tip 10 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/README.md: -------------------------------------------------------------------------------- 1 | context 2 | ======= 3 | [![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) 4 | 5 | gorilla/context is a general purpose registry for global request variables. 6 | 7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/context 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | mutex sync.RWMutex 15 | data = make(map[*http.Request]map[interface{}]interface{}) 16 | datat = make(map[*http.Request]int64) 17 | ) 18 | 19 | // Set stores a value for a given key in a given request. 20 | func Set(r *http.Request, key, val interface{}) { 21 | mutex.Lock() 22 | if data[r] == nil { 23 | data[r] = make(map[interface{}]interface{}) 24 | datat[r] = time.Now().Unix() 25 | } 26 | data[r][key] = val 27 | mutex.Unlock() 28 | } 29 | 30 | // Get returns a value stored for a given key in a given request. 31 | func Get(r *http.Request, key interface{}) interface{} { 32 | mutex.RLock() 33 | if ctx := data[r]; ctx != nil { 34 | value := ctx[key] 35 | mutex.RUnlock() 36 | return value 37 | } 38 | mutex.RUnlock() 39 | return nil 40 | } 41 | 42 | // GetOk returns stored value and presence state like multi-value return of map access. 43 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) { 44 | mutex.RLock() 45 | if _, ok := data[r]; ok { 46 | value, ok := data[r][key] 47 | mutex.RUnlock() 48 | return value, ok 49 | } 50 | mutex.RUnlock() 51 | return nil, false 52 | } 53 | 54 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. 55 | func GetAll(r *http.Request) map[interface{}]interface{} { 56 | mutex.RLock() 57 | if context, ok := data[r]; ok { 58 | result := make(map[interface{}]interface{}, len(context)) 59 | for k, v := range context { 60 | result[k] = v 61 | } 62 | mutex.RUnlock() 63 | return result 64 | } 65 | mutex.RUnlock() 66 | return nil 67 | } 68 | 69 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if 70 | // the request was registered. 71 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { 72 | mutex.RLock() 73 | context, ok := data[r] 74 | result := make(map[interface{}]interface{}, len(context)) 75 | for k, v := range context { 76 | result[k] = v 77 | } 78 | mutex.RUnlock() 79 | return result, ok 80 | } 81 | 82 | // Delete removes a value stored for a given key in a given request. 83 | func Delete(r *http.Request, key interface{}) { 84 | mutex.Lock() 85 | if data[r] != nil { 86 | delete(data[r], key) 87 | } 88 | mutex.Unlock() 89 | } 90 | 91 | // Clear removes all values stored for a given request. 92 | // 93 | // This is usually called by a handler wrapper to clean up request 94 | // variables at the end of a request lifetime. See ClearHandler(). 95 | func Clear(r *http.Request) { 96 | mutex.Lock() 97 | clear(r) 98 | mutex.Unlock() 99 | } 100 | 101 | // clear is Clear without the lock. 102 | func clear(r *http.Request) { 103 | delete(data, r) 104 | delete(datat, r) 105 | } 106 | 107 | // Purge removes request data stored for longer than maxAge, in seconds. 108 | // It returns the amount of requests removed. 109 | // 110 | // If maxAge <= 0, all request data is removed. 111 | // 112 | // This is only used for sanity check: in case context cleaning was not 113 | // properly set some request data can be kept forever, consuming an increasing 114 | // amount of memory. In case this is detected, Purge() must be called 115 | // periodically until the problem is fixed. 116 | func Purge(maxAge int) int { 117 | mutex.Lock() 118 | count := 0 119 | if maxAge <= 0 { 120 | count = len(data) 121 | data = make(map[*http.Request]map[interface{}]interface{}) 122 | datat = make(map[*http.Request]int64) 123 | } else { 124 | min := time.Now().Unix() - int64(maxAge) 125 | for r := range data { 126 | if datat[r] < min { 127 | clear(r) 128 | count++ 129 | } 130 | } 131 | } 132 | mutex.Unlock() 133 | return count 134 | } 135 | 136 | // ClearHandler wraps an http.Handler and clears request values at the end 137 | // of a request lifetime. 138 | func ClearHandler(h http.Handler) http.Handler { 139 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 140 | defer Clear(r) 141 | h.ServeHTTP(w, r) 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package context 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | type keyType int 13 | 14 | const ( 15 | key1 keyType = iota 16 | key2 17 | ) 18 | 19 | func TestContext(t *testing.T) { 20 | assertEqual := func(val interface{}, exp interface{}) { 21 | if val != exp { 22 | t.Errorf("Expected %v, got %v.", exp, val) 23 | } 24 | } 25 | 26 | r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 27 | emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 28 | 29 | // Get() 30 | assertEqual(Get(r, key1), nil) 31 | 32 | // Set() 33 | Set(r, key1, "1") 34 | assertEqual(Get(r, key1), "1") 35 | assertEqual(len(data[r]), 1) 36 | 37 | Set(r, key2, "2") 38 | assertEqual(Get(r, key2), "2") 39 | assertEqual(len(data[r]), 2) 40 | 41 | //GetOk 42 | value, ok := GetOk(r, key1) 43 | assertEqual(value, "1") 44 | assertEqual(ok, true) 45 | 46 | value, ok = GetOk(r, "not exists") 47 | assertEqual(value, nil) 48 | assertEqual(ok, false) 49 | 50 | Set(r, "nil value", nil) 51 | value, ok = GetOk(r, "nil value") 52 | assertEqual(value, nil) 53 | assertEqual(ok, true) 54 | 55 | // GetAll() 56 | values := GetAll(r) 57 | assertEqual(len(values), 3) 58 | 59 | // GetAll() for empty request 60 | values = GetAll(emptyR) 61 | if values != nil { 62 | t.Error("GetAll didn't return nil value for invalid request") 63 | } 64 | 65 | // GetAllOk() 66 | values, ok = GetAllOk(r) 67 | assertEqual(len(values), 3) 68 | assertEqual(ok, true) 69 | 70 | // GetAllOk() for empty request 71 | values, ok = GetAllOk(emptyR) 72 | assertEqual(value, nil) 73 | assertEqual(ok, false) 74 | 75 | // Delete() 76 | Delete(r, key1) 77 | assertEqual(Get(r, key1), nil) 78 | assertEqual(len(data[r]), 2) 79 | 80 | Delete(r, key2) 81 | assertEqual(Get(r, key2), nil) 82 | assertEqual(len(data[r]), 1) 83 | 84 | // Clear() 85 | Clear(r) 86 | assertEqual(len(data), 0) 87 | } 88 | 89 | func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) { 90 | <-wait 91 | for i := 0; i < iterations; i++ { 92 | Get(r, key) 93 | } 94 | done <- struct{}{} 95 | 96 | } 97 | 98 | func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) { 99 | <-wait 100 | for i := 0; i < iterations; i++ { 101 | Set(r, key, value) 102 | } 103 | done <- struct{}{} 104 | 105 | } 106 | 107 | func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) { 108 | 109 | b.StopTimer() 110 | r, _ := http.NewRequest("GET", "http://localhost:8080/", nil) 111 | done := make(chan struct{}) 112 | b.StartTimer() 113 | 114 | for i := 0; i < b.N; i++ { 115 | wait := make(chan struct{}) 116 | 117 | for i := 0; i < numReaders; i++ { 118 | go parallelReader(r, "test", iterations, wait, done) 119 | } 120 | 121 | for i := 0; i < numWriters; i++ { 122 | go parallelWriter(r, "test", "123", iterations, wait, done) 123 | } 124 | 125 | close(wait) 126 | 127 | for i := 0; i < numReaders+numWriters; i++ { 128 | <-done 129 | } 130 | 131 | } 132 | 133 | } 134 | 135 | func BenchmarkMutexSameReadWrite1(b *testing.B) { 136 | benchmarkMutex(b, 1, 1, 32) 137 | } 138 | func BenchmarkMutexSameReadWrite2(b *testing.B) { 139 | benchmarkMutex(b, 2, 2, 32) 140 | } 141 | func BenchmarkMutexSameReadWrite4(b *testing.B) { 142 | benchmarkMutex(b, 4, 4, 32) 143 | } 144 | func BenchmarkMutex1(b *testing.B) { 145 | benchmarkMutex(b, 2, 8, 32) 146 | } 147 | func BenchmarkMutex2(b *testing.B) { 148 | benchmarkMutex(b, 16, 4, 64) 149 | } 150 | func BenchmarkMutex3(b *testing.B) { 151 | benchmarkMutex(b, 1, 2, 128) 152 | } 153 | func BenchmarkMutex4(b *testing.B) { 154 | benchmarkMutex(b, 128, 32, 256) 155 | } 156 | func BenchmarkMutex5(b *testing.B) { 157 | benchmarkMutex(b, 1024, 2048, 64) 158 | } 159 | func BenchmarkMutex6(b *testing.B) { 160 | benchmarkMutex(b, 2048, 1024, 512) 161 | } 162 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/context/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package context stores values shared during a request lifetime. 7 | 8 | For example, a router can set variables extracted from the URL and later 9 | application handlers can access those values, or it can be used to store 10 | sessions values to be saved at the end of a request. There are several 11 | others common uses. 12 | 13 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: 14 | 15 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 16 | 17 | Here's the basic usage: first define the keys that you will need. The key 18 | type is interface{} so a key can be of any type that supports equality. 19 | Here we define a key using a custom int type to avoid name collisions: 20 | 21 | package foo 22 | 23 | import ( 24 | "github.com/gorilla/context" 25 | ) 26 | 27 | type key int 28 | 29 | const MyKey key = 0 30 | 31 | Then set a variable. Variables are bound to an http.Request object, so you 32 | need a request instance to set a value: 33 | 34 | context.Set(r, MyKey, "bar") 35 | 36 | The application can later access the variable using the same key you provided: 37 | 38 | func MyHandler(w http.ResponseWriter, r *http.Request) { 39 | // val is "bar". 40 | val := context.Get(r, foo.MyKey) 41 | 42 | // returns ("bar", true) 43 | val, ok := context.GetOk(r, foo.MyKey) 44 | // ... 45 | } 46 | 47 | And that's all about the basic usage. We discuss some other ideas below. 48 | 49 | Any type can be stored in the context. To enforce a given type, make the key 50 | private and wrap Get() and Set() to accept and return values of a specific 51 | type: 52 | 53 | type key int 54 | 55 | const mykey key = 0 56 | 57 | // GetMyKey returns a value for this package from the request values. 58 | func GetMyKey(r *http.Request) SomeType { 59 | if rv := context.Get(r, mykey); rv != nil { 60 | return rv.(SomeType) 61 | } 62 | return nil 63 | } 64 | 65 | // SetMyKey sets a value for this package in the request values. 66 | func SetMyKey(r *http.Request, val SomeType) { 67 | context.Set(r, mykey, val) 68 | } 69 | 70 | Variables must be cleared at the end of a request, to remove all values 71 | that were stored. This can be done in an http.Handler, after a request was 72 | served. Just call Clear() passing the request: 73 | 74 | context.Clear(r) 75 | 76 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear 77 | variables at the end of a request lifetime. 78 | 79 | The Routers from the packages gorilla/mux and gorilla/pat call Clear() 80 | so if you are using either of them you don't need to clear the context manually. 81 | */ 82 | package context 83 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - 1.2 7 | - tip 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/README.md: -------------------------------------------------------------------------------- 1 | mux 2 | === 3 | [![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux) 4 | 5 | gorilla/mux is a powerful URL router and dispatcher. 6 | 7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func BenchmarkMux(b *testing.B) { 13 | router := new(Router) 14 | handler := func(w http.ResponseWriter, r *http.Request) {} 15 | router.HandleFunc("/v1/{v1}", handler) 16 | 17 | request, _ := http.NewRequest("GET", "/v1/anything", nil) 18 | for i := 0; i < b.N; i++ { 19 | router.ServeHTTP(nil, request) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package gorilla/mux implements a request router and dispatcher. 7 | 8 | The name mux stands for "HTTP request multiplexer". Like the standard 9 | http.ServeMux, mux.Router matches incoming requests against a list of 10 | registered routes and calls a handler for the route that matches the URL 11 | or other conditions. The main features are: 12 | 13 | * Requests can be matched based on URL host, path, path prefix, schemes, 14 | header and query values, HTTP methods or using custom matchers. 15 | * URL hosts and paths can have variables with an optional regular 16 | expression. 17 | * Registered URLs can be built, or "reversed", which helps maintaining 18 | references to resources. 19 | * Routes can be used as subrouters: nested routes are only tested if the 20 | parent route matches. This is useful to define groups of routes that 21 | share common conditions like a host, a path prefix or other repeated 22 | attributes. As a bonus, this optimizes request matching. 23 | * It implements the http.Handler interface so it is compatible with the 24 | standard http.ServeMux. 25 | 26 | Let's start registering a couple of URL paths and handlers: 27 | 28 | func main() { 29 | r := mux.NewRouter() 30 | r.HandleFunc("/", HomeHandler) 31 | r.HandleFunc("/products", ProductsHandler) 32 | r.HandleFunc("/articles", ArticlesHandler) 33 | http.Handle("/", r) 34 | } 35 | 36 | Here we register three routes mapping URL paths to handlers. This is 37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches 38 | one of the paths, the corresponding handler is called passing 39 | (http.ResponseWriter, *http.Request) as parameters. 40 | 41 | Paths can have variables. They are defined using the format {name} or 42 | {name:pattern}. If a regular expression pattern is not defined, the matched 43 | variable will be anything until the next slash. For example: 44 | 45 | r := mux.NewRouter() 46 | r.HandleFunc("/products/{key}", ProductHandler) 47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) 48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) 49 | 50 | The names are used to create a map of route variables which can be retrieved 51 | calling mux.Vars(): 52 | 53 | vars := mux.Vars(request) 54 | category := vars["category"] 55 | 56 | And this is all you need to know about the basic usage. More advanced options 57 | are explained below. 58 | 59 | Routes can also be restricted to a domain or subdomain. Just define a host 60 | pattern to be matched. They can also have variables: 61 | 62 | r := mux.NewRouter() 63 | // Only matches if domain is "www.domain.com". 64 | r.Host("www.domain.com") 65 | // Matches a dynamic subdomain. 66 | r.Host("{subdomain:[a-z]+}.domain.com") 67 | 68 | There are several other matchers that can be added. To match path prefixes: 69 | 70 | r.PathPrefix("/products/") 71 | 72 | ...or HTTP methods: 73 | 74 | r.Methods("GET", "POST") 75 | 76 | ...or URL schemes: 77 | 78 | r.Schemes("https") 79 | 80 | ...or header values: 81 | 82 | r.Headers("X-Requested-With", "XMLHttpRequest") 83 | 84 | ...or query values: 85 | 86 | r.Queries("key", "value") 87 | 88 | ...or to use a custom matcher function: 89 | 90 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { 91 | return r.ProtoMajor == 0 92 | }) 93 | 94 | ...and finally, it is possible to combine several matchers in a single route: 95 | 96 | r.HandleFunc("/products", ProductsHandler). 97 | Host("www.domain.com"). 98 | Methods("GET"). 99 | Schemes("http") 100 | 101 | Setting the same matching conditions again and again can be boring, so we have 102 | a way to group several routes that share the same requirements. 103 | We call it "subrouting". 104 | 105 | For example, let's say we have several URLs that should only match when the 106 | host is "www.domain.com". Create a route for that host and get a "subrouter" 107 | from it: 108 | 109 | r := mux.NewRouter() 110 | s := r.Host("www.domain.com").Subrouter() 111 | 112 | Then register routes in the subrouter: 113 | 114 | s.HandleFunc("/products/", ProductsHandler) 115 | s.HandleFunc("/products/{key}", ProductHandler) 116 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) 117 | 118 | The three URL paths we registered above will only be tested if the domain is 119 | "www.domain.com", because the subrouter is tested first. This is not 120 | only convenient, but also optimizes request matching. You can create 121 | subrouters combining any attribute matchers accepted by a route. 122 | 123 | Subrouters can be used to create domain or path "namespaces": you define 124 | subrouters in a central place and then parts of the app can register its 125 | paths relatively to a given subrouter. 126 | 127 | There's one more thing about subroutes. When a subrouter has a path prefix, 128 | the inner routes use it as base for their paths: 129 | 130 | r := mux.NewRouter() 131 | s := r.PathPrefix("/products").Subrouter() 132 | // "/products/" 133 | s.HandleFunc("/", ProductsHandler) 134 | // "/products/{key}/" 135 | s.HandleFunc("/{key}/", ProductHandler) 136 | // "/products/{key}/details" 137 | s.HandleFunc("/{key}/details", ProductDetailsHandler) 138 | 139 | Now let's see how to build registered URLs. 140 | 141 | Routes can be named. All routes that define a name can have their URLs built, 142 | or "reversed". We define a name calling Name() on a route. For example: 143 | 144 | r := mux.NewRouter() 145 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). 146 | Name("article") 147 | 148 | To build a URL, get the route and call the URL() method, passing a sequence of 149 | key/value pairs for the route variables. For the previous route, we would do: 150 | 151 | url, err := r.Get("article").URL("category", "technology", "id", "42") 152 | 153 | ...and the result will be a url.URL with the following path: 154 | 155 | "/articles/technology/42" 156 | 157 | This also works for host variables: 158 | 159 | r := mux.NewRouter() 160 | r.Host("{subdomain}.domain.com"). 161 | Path("/articles/{category}/{id:[0-9]+}"). 162 | HandlerFunc(ArticleHandler). 163 | Name("article") 164 | 165 | // url.String() will be "http://news.domain.com/articles/technology/42" 166 | url, err := r.Get("article").URL("subdomain", "news", 167 | "category", "technology", 168 | "id", "42") 169 | 170 | All variables defined in the route are required, and their values must 171 | conform to the corresponding patterns. These requirements guarantee that a 172 | generated URL will always match a registered route -- the only exception is 173 | for explicitly defined "build-only" routes which never match. 174 | 175 | There's also a way to build only the URL host or path for a route: 176 | use the methods URLHost() or URLPath() instead. For the previous route, 177 | we would do: 178 | 179 | // "http://news.domain.com/" 180 | host, err := r.Get("article").URLHost("subdomain", "news") 181 | 182 | // "/articles/technology/42" 183 | path, err := r.Get("article").URLPath("category", "technology", "id", "42") 184 | 185 | And if you use subrouters, host and path defined separately can be built 186 | as well: 187 | 188 | r := mux.NewRouter() 189 | s := r.Host("{subdomain}.domain.com").Subrouter() 190 | s.Path("/articles/{category}/{id:[0-9]+}"). 191 | HandlerFunc(ArticleHandler). 192 | Name("article") 193 | 194 | // "http://news.domain.com/articles/technology/42" 195 | url, err := r.Get("article").URL("subdomain", "news", 196 | "category", "technology", 197 | "id", "42") 198 | */ 199 | package mux 200 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/gorilla/mux/regexp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mux 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "net/http" 11 | "net/url" 12 | "regexp" 13 | "strings" 14 | ) 15 | 16 | // newRouteRegexp parses a route template and returns a routeRegexp, 17 | // used to match a host, a path or a query string. 18 | // 19 | // It will extract named variables, assemble a regexp to be matched, create 20 | // a "reverse" template to build URLs and compile regexps to validate variable 21 | // values used in URL building. 22 | // 23 | // Previously we accepted only Python-like identifiers for variable 24 | // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that 25 | // name and pattern can't be empty, and names can't contain a colon. 26 | func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { 27 | // Check if it is well-formed. 28 | idxs, errBraces := braceIndices(tpl) 29 | if errBraces != nil { 30 | return nil, errBraces 31 | } 32 | // Backup the original. 33 | template := tpl 34 | // Now let's parse it. 35 | defaultPattern := "[^/]+" 36 | if matchQuery { 37 | defaultPattern = "[^?&]+" 38 | matchPrefix = true 39 | } else if matchHost { 40 | defaultPattern = "[^.]+" 41 | matchPrefix = false 42 | } 43 | // Only match strict slash if not matching 44 | if matchPrefix || matchHost || matchQuery { 45 | strictSlash = false 46 | } 47 | // Set a flag for strictSlash. 48 | endSlash := false 49 | if strictSlash && strings.HasSuffix(tpl, "/") { 50 | tpl = tpl[:len(tpl)-1] 51 | endSlash = true 52 | } 53 | varsN := make([]string, len(idxs)/2) 54 | varsR := make([]*regexp.Regexp, len(idxs)/2) 55 | pattern := bytes.NewBufferString("") 56 | if !matchQuery { 57 | pattern.WriteByte('^') 58 | } 59 | reverse := bytes.NewBufferString("") 60 | var end int 61 | var err error 62 | for i := 0; i < len(idxs); i += 2 { 63 | // Set all values we are interested in. 64 | raw := tpl[end:idxs[i]] 65 | end = idxs[i+1] 66 | parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) 67 | name := parts[0] 68 | patt := defaultPattern 69 | if len(parts) == 2 { 70 | patt = parts[1] 71 | } 72 | // Name or pattern can't be empty. 73 | if name == "" || patt == "" { 74 | return nil, fmt.Errorf("mux: missing name or pattern in %q", 75 | tpl[idxs[i]:end]) 76 | } 77 | // Build the regexp pattern. 78 | fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) 79 | // Build the reverse template. 80 | fmt.Fprintf(reverse, "%s%%s", raw) 81 | // Append variable name and compiled pattern. 82 | varsN[i/2] = name 83 | varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) 84 | if err != nil { 85 | return nil, err 86 | } 87 | } 88 | // Add the remaining. 89 | raw := tpl[end:] 90 | pattern.WriteString(regexp.QuoteMeta(raw)) 91 | if strictSlash { 92 | pattern.WriteString("[/]?") 93 | } 94 | if !matchPrefix { 95 | pattern.WriteByte('$') 96 | } 97 | reverse.WriteString(raw) 98 | if endSlash { 99 | reverse.WriteByte('/') 100 | } 101 | // Compile full regexp. 102 | reg, errCompile := regexp.Compile(pattern.String()) 103 | if errCompile != nil { 104 | return nil, errCompile 105 | } 106 | // Done! 107 | return &routeRegexp{ 108 | template: template, 109 | matchHost: matchHost, 110 | matchQuery: matchQuery, 111 | strictSlash: strictSlash, 112 | regexp: reg, 113 | reverse: reverse.String(), 114 | varsN: varsN, 115 | varsR: varsR, 116 | }, nil 117 | } 118 | 119 | // routeRegexp stores a regexp to match a host or path and information to 120 | // collect and validate route variables. 121 | type routeRegexp struct { 122 | // The unmodified template. 123 | template string 124 | // True for host match, false for path or query string match. 125 | matchHost bool 126 | // True for query string match, false for path and host match. 127 | matchQuery bool 128 | // The strictSlash value defined on the route, but disabled if PathPrefix was used. 129 | strictSlash bool 130 | // Expanded regexp. 131 | regexp *regexp.Regexp 132 | // Reverse template. 133 | reverse string 134 | // Variable names. 135 | varsN []string 136 | // Variable regexps (validators). 137 | varsR []*regexp.Regexp 138 | } 139 | 140 | // Match matches the regexp against the URL host or path. 141 | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { 142 | if !r.matchHost { 143 | if r.matchQuery { 144 | return r.regexp.MatchString(req.URL.RawQuery) 145 | } else { 146 | return r.regexp.MatchString(req.URL.Path) 147 | } 148 | } 149 | return r.regexp.MatchString(getHost(req)) 150 | } 151 | 152 | // url builds a URL part using the given values. 153 | func (r *routeRegexp) url(pairs ...string) (string, error) { 154 | values, err := mapFromPairs(pairs...) 155 | if err != nil { 156 | return "", err 157 | } 158 | urlValues := make([]interface{}, len(r.varsN)) 159 | for k, v := range r.varsN { 160 | value, ok := values[v] 161 | if !ok { 162 | return "", fmt.Errorf("mux: missing route variable %q", v) 163 | } 164 | urlValues[k] = value 165 | } 166 | rv := fmt.Sprintf(r.reverse, urlValues...) 167 | if !r.regexp.MatchString(rv) { 168 | // The URL is checked against the full regexp, instead of checking 169 | // individual variables. This is faster but to provide a good error 170 | // message, we check individual regexps if the URL doesn't match. 171 | for k, v := range r.varsN { 172 | if !r.varsR[k].MatchString(values[v]) { 173 | return "", fmt.Errorf( 174 | "mux: variable %q doesn't match, expected %q", values[v], 175 | r.varsR[k].String()) 176 | } 177 | } 178 | } 179 | return rv, nil 180 | } 181 | 182 | // braceIndices returns the first level curly brace indices from a string. 183 | // It returns an error in case of unbalanced braces. 184 | func braceIndices(s string) ([]int, error) { 185 | var level, idx int 186 | idxs := make([]int, 0) 187 | for i := 0; i < len(s); i++ { 188 | switch s[i] { 189 | case '{': 190 | if level++; level == 1 { 191 | idx = i 192 | } 193 | case '}': 194 | if level--; level == 0 { 195 | idxs = append(idxs, idx, i+1) 196 | } else if level < 0 { 197 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 198 | } 199 | } 200 | } 201 | if level != 0 { 202 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s) 203 | } 204 | return idxs, nil 205 | } 206 | 207 | // ---------------------------------------------------------------------------- 208 | // routeRegexpGroup 209 | // ---------------------------------------------------------------------------- 210 | 211 | // routeRegexpGroup groups the route matchers that carry variables. 212 | type routeRegexpGroup struct { 213 | host *routeRegexp 214 | path *routeRegexp 215 | queries []*routeRegexp 216 | } 217 | 218 | // setMatch extracts the variables from the URL once a route matches. 219 | func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { 220 | // Store host variables. 221 | if v.host != nil { 222 | hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) 223 | if hostVars != nil { 224 | for k, v := range v.host.varsN { 225 | m.Vars[v] = hostVars[k+1] 226 | } 227 | } 228 | } 229 | // Store path variables. 230 | if v.path != nil { 231 | pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) 232 | if pathVars != nil { 233 | for k, v := range v.path.varsN { 234 | m.Vars[v] = pathVars[k+1] 235 | } 236 | // Check if we should redirect. 237 | if v.path.strictSlash { 238 | p1 := strings.HasSuffix(req.URL.Path, "/") 239 | p2 := strings.HasSuffix(v.path.template, "/") 240 | if p1 != p2 { 241 | u, _ := url.Parse(req.URL.String()) 242 | if p1 { 243 | u.Path = u.Path[:len(u.Path)-1] 244 | } else { 245 | u.Path += "/" 246 | } 247 | m.Handler = http.RedirectHandler(u.String(), 301) 248 | } 249 | } 250 | } 251 | } 252 | // Store query string variables. 253 | rawQuery := req.URL.RawQuery 254 | for _, q := range v.queries { 255 | queryVars := q.regexp.FindStringSubmatch(rawQuery) 256 | if queryVars != nil { 257 | for k, v := range q.varsN { 258 | m.Vars[v] = queryVars[k+1] 259 | } 260 | } 261 | } 262 | } 263 | 264 | // getHost tries its best to return the request host. 265 | func getHost(r *http.Request) string { 266 | if r.URL.IsAbs() { 267 | return r.URL.Host 268 | } 269 | host := r.Host 270 | // Slice off any port information. 271 | if i := strings.Index(host, ":"); i != -1 { 272 | host = host[:i] 273 | } 274 | return host 275 | 276 | } 277 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/pmylund/go-cache/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This is a list of people who have contributed code to go-cache. They, or their 2 | employers, are the copyright holders of the contributed code. Contributed code 3 | is subject to the license restrictions listed in LICENSE (as they were when the 4 | code was contributed.) 5 | 6 | Dustin Sallings 7 | Jason Mooberry 8 | Sergey Shepelev 9 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/pmylund/go-cache/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Patrick Mylund Nielsen and the go-cache contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/pmylund/go-cache/README.md: -------------------------------------------------------------------------------- 1 | # go-cache 2 | 3 | go-cache is an in-memory key:value store/cache similar to memcached that is 4 | suitable for applications running on a single machine. Its major advantage is 5 | that, being essentially a thread-safe `map[string]interface{}` with expiration 6 | times, it doesn't need to serialize or transmit its contents over the network. 7 | 8 | Any object can be stored, for a given duration or forever, and the cache can be 9 | safely used by multiple goroutines. 10 | 11 | Although go-cache isn't meant to be used as a persistent datastore, the entire 12 | cache can be saved to and loaded from a file (using `c.Items()` to retrieve the 13 | items map to serialize, and `NewFrom()` to create a cache from a deserialized 14 | one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.) 15 | 16 | ### Installation 17 | 18 | `go get github.com/pmylund/go-cache` 19 | 20 | ### Usage 21 | 22 | import ( 23 | "fmt" 24 | "github.com/pmylund/go-cache" 25 | "time" 26 | ) 27 | 28 | func main() { 29 | 30 | // Create a cache with a default expiration time of 5 minutes, and which 31 | // purges expired items every 30 seconds 32 | c := cache.New(5*time.Minute, 30*time.Second) 33 | 34 | // Set the value of the key "foo" to "bar", with the default expiration time 35 | c.Set("foo", "bar", cache.DefaultExpiration) 36 | 37 | // Set the value of the key "baz" to 42, with no expiration time 38 | // (the item won't be removed until it is re-set, or removed using 39 | // c.Delete("baz") 40 | c.Set("baz", 42, cache.NoExpiration) 41 | 42 | // Get the string associated with the key "foo" from the cache 43 | foo, found := c.Get("foo") 44 | if found { 45 | fmt.Println(foo) 46 | } 47 | 48 | // Since Go is statically typed, and cache values can be anything, type 49 | // assertion is needed when values are being passed to functions that don't 50 | // take arbitrary types, (i.e. interface{}). The simplest way to do this for 51 | // values which will only be used once--e.g. for passing to another 52 | // function--is: 53 | foo, found := c.Get("foo") 54 | if found { 55 | MyFunction(foo.(string)) 56 | } 57 | 58 | // This gets tedious if the value is used several times in the same function. 59 | // You might do either of the following instead: 60 | if x, found := c.Get("foo"); found { 61 | foo := x.(string) 62 | // ... 63 | } 64 | // or 65 | var foo string 66 | if x, found := c.Get("foo"); found { 67 | foo = x.(string) 68 | } 69 | // ... 70 | // foo can then be passed around freely as a string 71 | 72 | // Want performance? Store pointers! 73 | c.Set("foo", &MyStruct, cache.DefaultExpiration) 74 | if x, found := c.Get("foo"); found { 75 | foo := x.(*MyStruct) 76 | // ... 77 | } 78 | 79 | // If you store a reference type like a pointer, slice, map or channel, you 80 | // do not need to run Set if you modify the underlying data. The cached 81 | // reference points to the same memory, so if you modify a struct whose 82 | // pointer you've stored in the cache, retrieving that pointer with Get will 83 | // point you to the same data: 84 | foo := &MyStruct{Num: 1} 85 | c.Set("foo", foo, cache.DefaultExpiration) 86 | // ... 87 | x, _ := c.Get("foo") 88 | foo := x.(*MyStruct) 89 | fmt.Println(foo.Num) 90 | // ... 91 | foo.Num++ 92 | // ... 93 | x, _ := c.Get("foo") 94 | foo := x.(*MyStruct) 95 | foo.Println(foo.Num) 96 | 97 | // will print: 98 | // 1 99 | // 2 100 | 101 | } 102 | 103 | 104 | ### Reference 105 | 106 | `godoc` or [http://godoc.org/github.com/pmylund/go-cache](http://godoc.org/github.com/pmylund/go-cache) 107 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/pmylund/go-cache/sharded.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | "math/big" 7 | insecurerand "math/rand" 8 | "os" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | // This is an experimental and unexported (for now) attempt at making a cache 14 | // with better algorithmic complexity than the standard one, namely by 15 | // preventing write locks of the entire cache when an item is added. As of the 16 | // time of writing, the overhead of selecting buckets results in cache 17 | // operations being about twice as slow as for the standard cache with small 18 | // total cache sizes, and faster for larger ones. 19 | // 20 | // See cache_test.go for a few benchmarks. 21 | 22 | type unexportedShardedCache struct { 23 | *shardedCache 24 | } 25 | 26 | type shardedCache struct { 27 | seed uint32 28 | m uint32 29 | cs []*cache 30 | janitor *shardedJanitor 31 | } 32 | 33 | // djb2 with better shuffling. 5x faster than FNV with the hash.Hash overhead. 34 | func djb33(seed uint32, k string) uint32 { 35 | var ( 36 | l = uint32(len(k)) 37 | d = 5381 + seed + l 38 | i = uint32(0) 39 | ) 40 | // Why is all this 5x faster than a for loop? 41 | if l >= 4 { 42 | for i < l-4 { 43 | d = (d * 33) ^ uint32(k[i]) 44 | d = (d * 33) ^ uint32(k[i+1]) 45 | d = (d * 33) ^ uint32(k[i+2]) 46 | d = (d * 33) ^ uint32(k[i+3]) 47 | i += 4 48 | } 49 | } 50 | switch l - i { 51 | case 1: 52 | case 2: 53 | d = (d * 33) ^ uint32(k[i]) 54 | case 3: 55 | d = (d * 33) ^ uint32(k[i]) 56 | d = (d * 33) ^ uint32(k[i+1]) 57 | case 4: 58 | d = (d * 33) ^ uint32(k[i]) 59 | d = (d * 33) ^ uint32(k[i+1]) 60 | d = (d * 33) ^ uint32(k[i+2]) 61 | } 62 | return d ^ (d >> 16) 63 | } 64 | 65 | func (sc *shardedCache) bucket(k string) *cache { 66 | return sc.cs[djb33(sc.seed, k)%sc.m] 67 | } 68 | 69 | func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) { 70 | sc.bucket(k).Set(k, x, d) 71 | } 72 | 73 | func (sc *shardedCache) Add(k string, x interface{}, d time.Duration) error { 74 | return sc.bucket(k).Add(k, x, d) 75 | } 76 | 77 | func (sc *shardedCache) Replace(k string, x interface{}, d time.Duration) error { 78 | return sc.bucket(k).Replace(k, x, d) 79 | } 80 | 81 | func (sc *shardedCache) Get(k string) (interface{}, bool) { 82 | return sc.bucket(k).Get(k) 83 | } 84 | 85 | func (sc *shardedCache) Increment(k string, n int64) error { 86 | return sc.bucket(k).Increment(k, n) 87 | } 88 | 89 | func (sc *shardedCache) IncrementFloat(k string, n float64) error { 90 | return sc.bucket(k).IncrementFloat(k, n) 91 | } 92 | 93 | func (sc *shardedCache) Decrement(k string, n int64) error { 94 | return sc.bucket(k).Decrement(k, n) 95 | } 96 | 97 | func (sc *shardedCache) Delete(k string) { 98 | sc.bucket(k).Delete(k) 99 | } 100 | 101 | func (sc *shardedCache) DeleteExpired() { 102 | for _, v := range sc.cs { 103 | v.DeleteExpired() 104 | } 105 | } 106 | 107 | // Returns the items in the cache. This may include items that have expired, 108 | // but have not yet been cleaned up. If this is significant, the Expiration 109 | // fields of the items should be checked. Note that explicit synchronization 110 | // is needed to use a cache and its corresponding Items() return values at 111 | // the same time, as the maps are shared. 112 | func (sc *shardedCache) Items() []map[string]*Item { 113 | res := make([]map[string]*Item, len(sc.cs)) 114 | for i, v := range sc.cs { 115 | res[i] = v.Items() 116 | } 117 | return res 118 | } 119 | 120 | func (sc *shardedCache) Flush() { 121 | for _, v := range sc.cs { 122 | v.Flush() 123 | } 124 | } 125 | 126 | type shardedJanitor struct { 127 | Interval time.Duration 128 | stop chan bool 129 | } 130 | 131 | func (j *shardedJanitor) Run(sc *shardedCache) { 132 | j.stop = make(chan bool) 133 | tick := time.Tick(j.Interval) 134 | for { 135 | select { 136 | case <-tick: 137 | sc.DeleteExpired() 138 | case <-j.stop: 139 | return 140 | } 141 | } 142 | } 143 | 144 | func stopShardedJanitor(sc *unexportedShardedCache) { 145 | sc.janitor.stop <- true 146 | } 147 | 148 | func runShardedJanitor(sc *shardedCache, ci time.Duration) { 149 | j := &shardedJanitor{ 150 | Interval: ci, 151 | } 152 | sc.janitor = j 153 | go j.Run(sc) 154 | } 155 | 156 | func newShardedCache(n int, de time.Duration) *shardedCache { 157 | max := big.NewInt(0).SetUint64(uint64(math.MaxUint32)) 158 | rnd, err := rand.Int(rand.Reader, max) 159 | var seed uint32 160 | if err != nil { 161 | os.Stderr.Write([]byte("WARNING: go-cache's newShardedCache failed to read from the system CSPRNG (/dev/urandom or equivalent.) Your system's security may be compromised. Continuing with an insecure seed.\n")) 162 | seed = insecurerand.Uint32() 163 | } else { 164 | seed = uint32(rnd.Uint64()) 165 | } 166 | sc := &shardedCache{ 167 | seed: seed, 168 | m: uint32(n), 169 | cs: make([]*cache, n), 170 | } 171 | for i := 0; i < n; i++ { 172 | c := &cache{ 173 | defaultExpiration: de, 174 | items: map[string]*Item{}, 175 | } 176 | sc.cs[i] = c 177 | } 178 | return sc 179 | } 180 | 181 | func unexportedNewSharded(defaultExpiration, cleanupInterval time.Duration, shards int) *unexportedShardedCache { 182 | if defaultExpiration == 0 { 183 | defaultExpiration = -1 184 | } 185 | sc := newShardedCache(shards, defaultExpiration) 186 | SC := &unexportedShardedCache{sc} 187 | if cleanupInterval > 0 { 188 | runShardedJanitor(sc, cleanupInterval) 189 | runtime.SetFinalizer(SC, stopShardedJanitor) 190 | } 191 | return SC 192 | } 193 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/pmylund/go-cache/sharded_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | // func TestDjb33(t *testing.T) { 10 | // } 11 | 12 | var shardedKeys = []string{ 13 | "f", 14 | "fo", 15 | "foo", 16 | "barf", 17 | "barfo", 18 | "foobar", 19 | "bazbarf", 20 | "bazbarfo", 21 | "bazbarfoo", 22 | "foobarbazq", 23 | "foobarbazqu", 24 | "foobarbazquu", 25 | "foobarbazquux", 26 | } 27 | 28 | func TestShardedCache(t *testing.T) { 29 | tc := unexportedNewSharded(DefaultExpiration, 0, 13) 30 | for _, v := range shardedKeys { 31 | tc.Set(v, "value", DefaultExpiration) 32 | } 33 | } 34 | 35 | func BenchmarkShardedCacheGet(b *testing.B) { 36 | b.StopTimer() 37 | tc := unexportedNewSharded(DefaultExpiration, 0, 10) 38 | tc.Set("foobarba", "zquux", DefaultExpiration) 39 | b.StartTimer() 40 | for i := 0; i < b.N; i++ { 41 | tc.Get("foobarba") 42 | } 43 | } 44 | 45 | func BenchmarkShardedCacheGetManyConcurrent(b *testing.B) { 46 | b.StopTimer() 47 | n := 10000 48 | tsc := unexportedNewSharded(DefaultExpiration, 0, 20) 49 | keys := make([]string, n) 50 | for i := 0; i < n; i++ { 51 | k := "foo" + strconv.Itoa(n) 52 | keys[i] = k 53 | tsc.Set(k, "bar", DefaultExpiration) 54 | } 55 | each := b.N / n 56 | wg := new(sync.WaitGroup) 57 | wg.Add(n) 58 | for _, v := range keys { 59 | go func() { 60 | for j := 0; j < each; j++ { 61 | tsc.Get(v) 62 | } 63 | wg.Done() 64 | }() 65 | } 66 | b.StartTimer() 67 | wg.Wait() 68 | } 69 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3 4 | - 1.4 5 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/README.md: -------------------------------------------------------------------------------- 1 | # Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) 2 | 3 | CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. 4 | 5 | ## Getting Started 6 | 7 | After installing Go and setting up your [GOPATH](http://golang.org/doc/code.html#GOPATH), create your first `.go` file. We'll call it `server.go`. 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "net/http" 14 | 15 | "github.com/rs/cors" 16 | ) 17 | 18 | func main() { 19 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | w.Header().Set("Content-Type", "application/json") 21 | w.Write([]byte("{\"hello\": \"world\"}")) 22 | }) 23 | 24 | // cors.Default() setup the middleware with default options being 25 | // all origins accepted with simple methods (GET, POST). See 26 | // documentation below for more options. 27 | handler = cors.Default().Handler(h) 28 | http.ListenAndServe(":8080", handler) 29 | } 30 | ``` 31 | 32 | Install `cors`: 33 | 34 | go get github.com/rs/cors 35 | 36 | Then run your server: 37 | 38 | go run server.go 39 | 40 | The server now runs on `localhost:8080`: 41 | 42 | $ curl -D - -H 'Origin: http://foo.com' http://localhost:8080/ 43 | HTTP/1.1 200 OK 44 | Access-Control-Allow-Origin: foo.com 45 | Content-Type: application/json 46 | Date: Sat, 25 Oct 2014 03:43:57 GMT 47 | Content-Length: 18 48 | 49 | {"hello": "world"} 50 | 51 | ### More Examples 52 | 53 | * `net/http`: [examples/nethttp/server.go](https://github.com/rs/cors/blob/master/examples/nethttp/server.go) 54 | * [Goji](https://goji.io): [examples/goji/server.go](https://github.com/rs/cors/blob/master/examples/goji/server.go) 55 | * [Martini](http://martini.codegangsta.io): [examples/martini/server.go](https://github.com/rs/cors/blob/master/examples/martini/server.go) 56 | * [Negroni](https://github.com/codegangsta/negroni): [examples/negroni/server.go](https://github.com/rs/cors/blob/master/examples/negroni/server.go) 57 | * [Alice](https://github.com/justinas/alice): [examples/alice/server.go](https://github.com/rs/cors/blob/master/examples/alice/server.go) 58 | 59 | ## Parameters 60 | 61 | Parameters are passed to the middleware thru the `cors.New` method as follow: 62 | 63 | ```go 64 | c := cors.New(cors.Options{ 65 | AllowedOrigins: []string{"http://foo.com"}, 66 | AllowCredentials: true, 67 | }) 68 | 69 | // Insert the middleware 70 | handler = c.Handler(handler) 71 | ``` 72 | 73 | * **AllowedOrigins** `[]string`: A list of origins a cross-domain request can be executed from. If the special `*` value is present in the list, all origins will be allowed. The default value is `*`. 74 | * **AllowedMethods** `[]string`: A list of methods the client is allowed to use with cross-domain requests. 75 | * **AllowedHeaders** `[]string`: A list of non simple headers the client is allowed to use with cross-domain requests. Default value is simple methods (`GET` and `POST`) 76 | * **ExposedHeaders** `[]string`: Indicates which headers are safe to expose to the API of a CORS API specification 77 | * **AllowCredentials** `bool`: Indicates whether the request can include user credentials like cookies, HTTP authentication or client side SSL certificates. The default is `false`. 78 | * **MaxAge** `int`: Indicates how long (in seconds) the results of a preflight request can be cached. The default is `0` which stands for no max age. 79 | 80 | See [API documentation](http://godoc.org/github.com/rs/cors) for more info. 81 | 82 | ## Licenses 83 | 84 | All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). 85 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/bench_test.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkWithout(b *testing.B) { 10 | res := httptest.NewRecorder() 11 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 12 | 13 | for i := 0; i < b.N; i++ { 14 | testHandler.ServeHTTP(res, req) 15 | } 16 | } 17 | 18 | func BenchmarkDefault(b *testing.B) { 19 | res := httptest.NewRecorder() 20 | req, _ := http.NewRequest("GET", "http://example.com/foo", nil) 21 | handler := Default() 22 | 23 | for i := 0; i < b.N; i++ { 24 | handler.Handler(testHandler).ServeHTTP(res, req) 25 | } 26 | } 27 | 28 | func BenchmarkPreflight(b *testing.B) { 29 | res := httptest.NewRecorder() 30 | req, _ := http.NewRequest("OPTIONS", "http://example.com/foo", nil) 31 | req.Header.Add("Access-Control-Request-Method", "GET") 32 | handler := Default() 33 | 34 | for i := 0; i < b.N; i++ { 35 | handler.Handler(testHandler).ServeHTTP(res, req) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/alice/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/justinas/alice" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | 15 | mux := http.NewServeMux() 16 | 17 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "application/json") 19 | w.Write([]byte("{\"hello\": \"world\"}")) 20 | }) 21 | 22 | chain := alice.New(c.Handler).Then(mux) 23 | http.ListenAndServe(":8080", chain) 24 | } 25 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/default/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | w.Header().Set("Content-Type", "application/json") 12 | w.Write([]byte("{\"hello\": \"world\"}")) 13 | }) 14 | 15 | // Use default options 16 | handler := cors.Default().Handler(h) 17 | http.ListenAndServe(":8080", handler) 18 | } 19 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/goji/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | "github.com/zenazn/goji" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | goji.Use(c.Handler) 15 | 16 | goji.Get("/", func(w http.ResponseWriter, r *http.Request) { 17 | w.Header().Set("Content-Type", "application/json") 18 | w.Write([]byte("{\"hello\": \"world\"}")) 19 | }) 20 | 21 | goji.Serve() 22 | } 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/martini/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/go-martini/martini" 5 | "github.com/martini-contrib/render" 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"http://foo.com"}, 12 | }) 13 | 14 | m := martini.Classic() 15 | m.Use(render.Renderer()) 16 | m.Use(c.HandlerFunc) 17 | 18 | m.Get("/", func(r render.Render) { 19 | r.JSON(200, map[string]interface{}{"hello": "world"}) 20 | }) 21 | 22 | m.Run() 23 | } 24 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/negroni/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/codegangsta/negroni" 7 | "github.com/rs/cors" 8 | ) 9 | 10 | func main() { 11 | c := cors.New(cors.Options{ 12 | AllowedOrigins: []string{"http://foo.com"}, 13 | }) 14 | 15 | mux := http.NewServeMux() 16 | 17 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | w.Header().Set("Content-Type", "application/json") 19 | w.Write([]byte("{\"hello\": \"world\"}")) 20 | }) 21 | 22 | n := negroni.Classic() 23 | n.Use(c) 24 | n.UseHandler(mux) 25 | n.Run(":3000") 26 | } 27 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/nethttp/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"http://foo.com"}, 12 | }) 13 | 14 | handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | w.Header().Set("Content-Type", "application/json") 16 | w.Write([]byte("{\"hello\": \"world\"}")) 17 | }) 18 | 19 | http.ListenAndServe(":8080", c.Handler(handler)) 20 | } 21 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/examples/openbar/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/rs/cors" 7 | ) 8 | 9 | func main() { 10 | c := cors.New(cors.Options{ 11 | AllowedOrigins: []string{"*"}, 12 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, 13 | AllowCredentials: true, 14 | }) 15 | 16 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | w.Header().Set("Content-Type", "application/json") 18 | w.Write([]byte("{\"hello\": \"world\"}")) 19 | }) 20 | 21 | http.ListenAndServe(":8080", c.Handler(h)) 22 | } 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/utils.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | type converter func(string) string 9 | 10 | // convert converts a list of string using the passed converter function 11 | func convert(s []string, c converter) []string { 12 | out := []string{} 13 | for _, i := range s { 14 | out = append(out, c(i)) 15 | } 16 | return out 17 | } 18 | 19 | func parseHeaderList(headerList string) (headers []string) { 20 | for _, header := range strings.Split(headerList, ",") { 21 | header = http.CanonicalHeaderKey(strings.TrimSpace(header)) 22 | if header != "" { 23 | headers = append(headers, header) 24 | } 25 | } 26 | return headers 27 | } 28 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/rs/cors/utils_test.go: -------------------------------------------------------------------------------- 1 | package cors 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestConvert(t *testing.T) { 9 | s := convert([]string{"A", "b", "C"}, strings.ToLower) 10 | e := []string{"a", "b", "c"} 11 | if s[0] != e[0] || s[1] != e[1] || s[2] != e[2] { 12 | t.Errorf("%v != %v", s, e) 13 | } 14 | } 15 | 16 | func TestParseHeaderList(t *testing.T) { 17 | h := parseHeaderList("header, second-header, THIRD-HEADER") 18 | e := []string{"Header", "Second-Header", "Third-Header"} 19 | if h[0] != e[0] || h[1] != e[1] || h[2] != e[2] { 20 | t.Errorf("%v != %v", h, e) 21 | } 22 | } 23 | 24 | func TestParseHeaderListEmpty(t *testing.T) { 25 | if len(parseHeaderList("")) != 0 { 26 | t.Error("should be empty sclice") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/LICENSE.md: -------------------------------------------------------------------------------- 1 | objx - by Mat Ryer and Tyler Bunnell 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2014 Stretchr, Inc. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/README.md: -------------------------------------------------------------------------------- 1 | # objx 2 | 3 | * Jump into the [API Documentation](http://godoc.org/github.com/stretchr/objx) 4 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/accessors.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // arrayAccesRegexString is the regex used to extract the array number 11 | // from the access path 12 | const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` 13 | 14 | // arrayAccesRegex is the compiled arrayAccesRegexString 15 | var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) 16 | 17 | // Get gets the value using the specified selector and 18 | // returns it inside a new Obj object. 19 | // 20 | // If it cannot find the value, Get will return a nil 21 | // value inside an instance of Obj. 22 | // 23 | // Get can only operate directly on map[string]interface{} and []interface. 24 | // 25 | // Example 26 | // 27 | // To access the title of the third chapter of the second book, do: 28 | // 29 | // o.Get("books[1].chapters[2].title") 30 | func (m Map) Get(selector string) *Value { 31 | rawObj := access(m, selector, nil, false, false) 32 | return &Value{data: rawObj} 33 | } 34 | 35 | // Set sets the value using the specified selector and 36 | // returns the object on which Set was called. 37 | // 38 | // Set can only operate directly on map[string]interface{} and []interface 39 | // 40 | // Example 41 | // 42 | // To set the title of the third chapter of the second book, do: 43 | // 44 | // o.Set("books[1].chapters[2].title","Time to Go") 45 | func (m Map) Set(selector string, value interface{}) Map { 46 | access(m, selector, value, true, false) 47 | return m 48 | } 49 | 50 | // access accesses the object using the selector and performs the 51 | // appropriate action. 52 | func access(current, selector, value interface{}, isSet, panics bool) interface{} { 53 | 54 | switch selector.(type) { 55 | case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 56 | 57 | if array, ok := current.([]interface{}); ok { 58 | index := intFromInterface(selector) 59 | 60 | if index >= len(array) { 61 | if panics { 62 | panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) 63 | } 64 | return nil 65 | } 66 | 67 | return array[index] 68 | } 69 | 70 | return nil 71 | 72 | case string: 73 | 74 | selStr := selector.(string) 75 | selSegs := strings.SplitN(selStr, PathSeparator, 2) 76 | thisSel := selSegs[0] 77 | index := -1 78 | var err error 79 | 80 | // https://github.com/stretchr/objx/issues/12 81 | if strings.Contains(thisSel, "[") { 82 | 83 | arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) 84 | 85 | if len(arrayMatches) > 0 { 86 | 87 | // Get the key into the map 88 | thisSel = arrayMatches[1] 89 | 90 | // Get the index into the array at the key 91 | index, err = strconv.Atoi(arrayMatches[2]) 92 | 93 | if err != nil { 94 | // This should never happen. If it does, something has gone 95 | // seriously wrong. Panic. 96 | panic("objx: Array index is not an integer. Must use array[int].") 97 | } 98 | 99 | } 100 | } 101 | 102 | if curMap, ok := current.(Map); ok { 103 | current = map[string]interface{}(curMap) 104 | } 105 | 106 | // get the object in question 107 | switch current.(type) { 108 | case map[string]interface{}: 109 | curMSI := current.(map[string]interface{}) 110 | if len(selSegs) <= 1 && isSet { 111 | curMSI[thisSel] = value 112 | return nil 113 | } else { 114 | current = curMSI[thisSel] 115 | } 116 | default: 117 | current = nil 118 | } 119 | 120 | if current == nil && panics { 121 | panic(fmt.Sprintf("objx: '%v' invalid on object.", selector)) 122 | } 123 | 124 | // do we need to access the item of an array? 125 | if index > -1 { 126 | if array, ok := current.([]interface{}); ok { 127 | if index < len(array) { 128 | current = array[index] 129 | } else { 130 | if panics { 131 | panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array))) 132 | } 133 | current = nil 134 | } 135 | } 136 | } 137 | 138 | if len(selSegs) > 1 { 139 | current = access(current, selSegs[1], value, isSet, panics) 140 | } 141 | 142 | } 143 | 144 | return current 145 | 146 | } 147 | 148 | // intFromInterface converts an interface object to the largest 149 | // representation of an unsigned integer using a type switch and 150 | // assertions 151 | func intFromInterface(selector interface{}) int { 152 | var value int 153 | switch selector.(type) { 154 | case int: 155 | value = selector.(int) 156 | case int8: 157 | value = int(selector.(int8)) 158 | case int16: 159 | value = int(selector.(int16)) 160 | case int32: 161 | value = int(selector.(int32)) 162 | case int64: 163 | value = int(selector.(int64)) 164 | case uint: 165 | value = int(selector.(uint)) 166 | case uint8: 167 | value = int(selector.(uint8)) 168 | case uint16: 169 | value = int(selector.(uint16)) 170 | case uint32: 171 | value = int(selector.(uint32)) 172 | case uint64: 173 | value = int(selector.(uint64)) 174 | default: 175 | panic("objx: array access argument is not an integer type (this should never happen)") 176 | } 177 | 178 | return value 179 | } 180 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/accessors_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestAccessorsAccessGetSingleField(t *testing.T) { 9 | 10 | current := map[string]interface{}{"name": "Tyler"} 11 | assert.Equal(t, "Tyler", access(current, "name", nil, false, true)) 12 | 13 | } 14 | func TestAccessorsAccessGetDeep(t *testing.T) { 15 | 16 | current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}} 17 | assert.Equal(t, "Tyler", access(current, "name.first", nil, false, true)) 18 | assert.Equal(t, "Bunnell", access(current, "name.last", nil, false, true)) 19 | 20 | } 21 | func TestAccessorsAccessGetDeepDeep(t *testing.T) { 22 | 23 | current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}} 24 | assert.Equal(t, 4, access(current, "one.two.three.four", nil, false, true)) 25 | 26 | } 27 | func TestAccessorsAccessGetInsideArray(t *testing.T) { 28 | 29 | current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}} 30 | assert.Equal(t, "Tyler", access(current, "names[0].first", nil, false, true)) 31 | assert.Equal(t, "Bunnell", access(current, "names[0].last", nil, false, true)) 32 | assert.Equal(t, "Capitol", access(current, "names[1].first", nil, false, true)) 33 | assert.Equal(t, "Bollocks", access(current, "names[1].last", nil, false, true)) 34 | 35 | assert.Panics(t, func() { 36 | access(current, "names[2]", nil, false, true) 37 | }) 38 | assert.Nil(t, access(current, "names[2]", nil, false, false)) 39 | 40 | } 41 | 42 | func TestAccessorsAccessGetFromArrayWithInt(t *testing.T) { 43 | 44 | current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}} 45 | one := access(current, 0, nil, false, false) 46 | two := access(current, 1, nil, false, false) 47 | three := access(current, 2, nil, false, false) 48 | 49 | assert.Equal(t, "Tyler", one.(map[string]interface{})["first"]) 50 | assert.Equal(t, "Capitol", two.(map[string]interface{})["first"]) 51 | assert.Nil(t, three) 52 | 53 | } 54 | 55 | func TestAccessorsGet(t *testing.T) { 56 | 57 | current := New(map[string]interface{}{"name": "Tyler"}) 58 | assert.Equal(t, "Tyler", current.Get("name").data) 59 | 60 | } 61 | 62 | func TestAccessorsAccessSetSingleField(t *testing.T) { 63 | 64 | current := map[string]interface{}{"name": "Tyler"} 65 | access(current, "name", "Mat", true, false) 66 | assert.Equal(t, current["name"], "Mat") 67 | 68 | access(current, "age", 29, true, true) 69 | assert.Equal(t, current["age"], 29) 70 | 71 | } 72 | 73 | func TestAccessorsAccessSetSingleFieldNotExisting(t *testing.T) { 74 | 75 | current := map[string]interface{}{} 76 | access(current, "name", "Mat", true, false) 77 | assert.Equal(t, current["name"], "Mat") 78 | 79 | } 80 | 81 | func TestAccessorsAccessSetDeep(t *testing.T) { 82 | 83 | current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}} 84 | 85 | access(current, "name.first", "Mat", true, true) 86 | access(current, "name.last", "Ryer", true, true) 87 | 88 | assert.Equal(t, "Mat", access(current, "name.first", nil, false, true)) 89 | assert.Equal(t, "Ryer", access(current, "name.last", nil, false, true)) 90 | 91 | } 92 | func TestAccessorsAccessSetDeepDeep(t *testing.T) { 93 | 94 | current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}} 95 | 96 | access(current, "one.two.three.four", 5, true, true) 97 | 98 | assert.Equal(t, 5, access(current, "one.two.three.four", nil, false, true)) 99 | 100 | } 101 | func TestAccessorsAccessSetArray(t *testing.T) { 102 | 103 | current := map[string]interface{}{"names": []interface{}{"Tyler"}} 104 | 105 | access(current, "names[0]", "Mat", true, true) 106 | 107 | assert.Equal(t, "Mat", access(current, "names[0]", nil, false, true)) 108 | 109 | } 110 | func TestAccessorsAccessSetInsideArray(t *testing.T) { 111 | 112 | current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}} 113 | 114 | access(current, "names[0].first", "Mat", true, true) 115 | access(current, "names[0].last", "Ryer", true, true) 116 | access(current, "names[1].first", "Captain", true, true) 117 | access(current, "names[1].last", "Underpants", true, true) 118 | 119 | assert.Equal(t, "Mat", access(current, "names[0].first", nil, false, true)) 120 | assert.Equal(t, "Ryer", access(current, "names[0].last", nil, false, true)) 121 | assert.Equal(t, "Captain", access(current, "names[1].first", nil, false, true)) 122 | assert.Equal(t, "Underpants", access(current, "names[1].last", nil, false, true)) 123 | 124 | } 125 | 126 | func TestAccessorsAccessSetFromArrayWithInt(t *testing.T) { 127 | 128 | current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}} 129 | one := access(current, 0, nil, false, false) 130 | two := access(current, 1, nil, false, false) 131 | three := access(current, 2, nil, false, false) 132 | 133 | assert.Equal(t, "Tyler", one.(map[string]interface{})["first"]) 134 | assert.Equal(t, "Capitol", two.(map[string]interface{})["first"]) 135 | assert.Nil(t, three) 136 | 137 | } 138 | 139 | func TestAccessorsSet(t *testing.T) { 140 | 141 | current := New(map[string]interface{}{"name": "Tyler"}) 142 | current.Set("name", "Mat") 143 | assert.Equal(t, "Mat", current.Get("name").data) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/codegen/array-access.txt: -------------------------------------------------------------------------------- 1 | case []{1}: 2 | a := object.([]{1}) 3 | if isSet { 4 | a[index] = value.({1}) 5 | } else { 6 | if index >= len(a) { 7 | if panics { 8 | panic(fmt.Sprintf("objx: Index %d is out of range because the []{1} only contains %d items.", index, len(a))) 9 | } 10 | return nil 11 | } else { 12 | return a[index] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/codegen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Codegen 5 | 6 | 17 | 18 | 19 | 20 |

21 | Template 22 |

23 |

24 | Use {x} as a placeholder for each argument. 25 |

26 | 27 | 28 |

29 | Arguments (comma separated) 30 |

31 |

32 | One block per line 33 |

34 | 35 | 36 |

37 | Output 38 |

39 | 40 | 41 | 42 | 43 | 44 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/codegen/template.txt: -------------------------------------------------------------------------------- 1 | /* 2 | {4} ({1} and []{1}) 3 | -------------------------------------------------- 4 | */ 5 | 6 | // {4} gets the value as a {1}, returns the optionalDefault 7 | // value or a system default object if the value is the wrong type. 8 | func (v *Value) {4}(optionalDefault ...{1}) {1} { 9 | if s, ok := v.data.({1}); ok { 10 | return s 11 | } 12 | if len(optionalDefault) == 1 { 13 | return optionalDefault[0] 14 | } 15 | return {3} 16 | } 17 | 18 | // Must{4} gets the value as a {1}. 19 | // 20 | // Panics if the object is not a {1}. 21 | func (v *Value) Must{4}() {1} { 22 | return v.data.({1}) 23 | } 24 | 25 | // {4}Slice gets the value as a []{1}, returns the optionalDefault 26 | // value or nil if the value is not a []{1}. 27 | func (v *Value) {4}Slice(optionalDefault ...[]{1}) []{1} { 28 | if s, ok := v.data.([]{1}); ok { 29 | return s 30 | } 31 | if len(optionalDefault) == 1 { 32 | return optionalDefault[0] 33 | } 34 | return nil 35 | } 36 | 37 | // Must{4}Slice gets the value as a []{1}. 38 | // 39 | // Panics if the object is not a []{1}. 40 | func (v *Value) Must{4}Slice() []{1} { 41 | return v.data.([]{1}) 42 | } 43 | 44 | // Is{4} gets whether the object contained is a {1} or not. 45 | func (v *Value) Is{4}() bool { 46 | _, ok := v.data.({1}) 47 | return ok 48 | } 49 | 50 | // Is{4}Slice gets whether the object contained is a []{1} or not. 51 | func (v *Value) Is{4}Slice() bool { 52 | _, ok := v.data.([]{1}) 53 | return ok 54 | } 55 | 56 | // Each{4} calls the specified callback for each object 57 | // in the []{1}. 58 | // 59 | // Panics if the object is the wrong type. 60 | func (v *Value) Each{4}(callback func(int, {1}) bool) *Value { 61 | 62 | for index, val := range v.Must{4}Slice() { 63 | carryon := callback(index, val) 64 | if carryon == false { 65 | break 66 | } 67 | } 68 | 69 | return v 70 | 71 | } 72 | 73 | // Where{4} uses the specified decider function to select items 74 | // from the []{1}. The object contained in the result will contain 75 | // only the selected items. 76 | func (v *Value) Where{4}(decider func(int, {1}) bool) *Value { 77 | 78 | var selected []{1} 79 | 80 | v.Each{4}(func(index int, val {1}) bool { 81 | shouldSelect := decider(index, val) 82 | if shouldSelect == false { 83 | selected = append(selected, val) 84 | } 85 | return true 86 | }) 87 | 88 | return &Value{data:selected} 89 | 90 | } 91 | 92 | // Group{4} uses the specified grouper function to group the items 93 | // keyed by the return of the grouper. The object contained in the 94 | // result will contain a map[string][]{1}. 95 | func (v *Value) Group{4}(grouper func(int, {1}) string) *Value { 96 | 97 | groups := make(map[string][]{1}) 98 | 99 | v.Each{4}(func(index int, val {1}) bool { 100 | group := grouper(index, val) 101 | if _, ok := groups[group]; !ok { 102 | groups[group] = make([]{1}, 0) 103 | } 104 | groups[group] = append(groups[group], val) 105 | return true 106 | }) 107 | 108 | return &Value{data:groups} 109 | 110 | } 111 | 112 | // Replace{4} uses the specified function to replace each {1}s 113 | // by iterating each item. The data in the returned result will be a 114 | // []{1} containing the replaced items. 115 | func (v *Value) Replace{4}(replacer func(int, {1}) {1}) *Value { 116 | 117 | arr := v.Must{4}Slice() 118 | replaced := make([]{1}, len(arr)) 119 | 120 | v.Each{4}(func(index int, val {1}) bool { 121 | replaced[index] = replacer(index, val) 122 | return true 123 | }) 124 | 125 | return &Value{data:replaced} 126 | 127 | } 128 | 129 | // Collect{4} uses the specified collector function to collect a value 130 | // for each of the {1}s in the slice. The data returned will be a 131 | // []interface{}. 132 | func (v *Value) Collect{4}(collector func(int, {1}) interface{}) *Value { 133 | 134 | arr := v.Must{4}Slice() 135 | collected := make([]interface{}, len(arr)) 136 | 137 | v.Each{4}(func(index int, val {1}) bool { 138 | collected[index] = collector(index, val) 139 | return true 140 | }) 141 | 142 | return &Value{data:collected} 143 | } 144 | 145 | // ************************************************************ 146 | // TESTS 147 | // ************************************************************ 148 | 149 | func Test{4}(t *testing.T) { 150 | 151 | val := {1}( {2} ) 152 | m := map[string]interface{}{"value": val, "nothing": nil} 153 | assert.Equal(t, val, New(m).Get("value").{4}()) 154 | assert.Equal(t, val, New(m).Get("value").Must{4}()) 155 | assert.Equal(t, {1}({3}), New(m).Get("nothing").{4}()) 156 | assert.Equal(t, val, New(m).Get("nothing").{4}({2})) 157 | 158 | assert.Panics(t, func() { 159 | New(m).Get("age").Must{4}() 160 | }) 161 | 162 | } 163 | 164 | func Test{4}Slice(t *testing.T) { 165 | 166 | val := {1}( {2} ) 167 | m := map[string]interface{}{"value": []{1}{ val }, "nothing": nil} 168 | assert.Equal(t, val, New(m).Get("value").{4}Slice()[0]) 169 | assert.Equal(t, val, New(m).Get("value").Must{4}Slice()[0]) 170 | assert.Equal(t, []{1}(nil), New(m).Get("nothing").{4}Slice()) 171 | assert.Equal(t, val, New(m).Get("nothing").{4}Slice( []{1}{ {1}({2}) } )[0]) 172 | 173 | assert.Panics(t, func() { 174 | New(m).Get("nothing").Must{4}Slice() 175 | }) 176 | 177 | } 178 | 179 | func TestIs{4}(t *testing.T) { 180 | 181 | var v *Value 182 | 183 | v = &Value{data: {1}({2})} 184 | assert.True(t, v.Is{4}()) 185 | 186 | v = &Value{data: []{1}{ {1}({2}) }} 187 | assert.True(t, v.Is{4}Slice()) 188 | 189 | } 190 | 191 | func TestEach{4}(t *testing.T) { 192 | 193 | v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} 194 | count := 0 195 | replacedVals := make([]{1}, 0) 196 | assert.Equal(t, v, v.Each{4}(func(i int, val {1}) bool { 197 | 198 | count++ 199 | replacedVals = append(replacedVals, val) 200 | 201 | // abort early 202 | if i == 2 { 203 | return false 204 | } 205 | 206 | return true 207 | 208 | })) 209 | 210 | assert.Equal(t, count, 3) 211 | assert.Equal(t, replacedVals[0], v.Must{4}Slice()[0]) 212 | assert.Equal(t, replacedVals[1], v.Must{4}Slice()[1]) 213 | assert.Equal(t, replacedVals[2], v.Must{4}Slice()[2]) 214 | 215 | } 216 | 217 | func TestWhere{4}(t *testing.T) { 218 | 219 | v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} 220 | 221 | selected := v.Where{4}(func(i int, val {1}) bool { 222 | return i%2==0 223 | }).Must{4}Slice() 224 | 225 | assert.Equal(t, 3, len(selected)) 226 | 227 | } 228 | 229 | func TestGroup{4}(t *testing.T) { 230 | 231 | v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} 232 | 233 | grouped := v.Group{4}(func(i int, val {1}) string { 234 | return fmt.Sprintf("%v", i%2==0) 235 | }).data.(map[string][]{1}) 236 | 237 | assert.Equal(t, 2, len(grouped)) 238 | assert.Equal(t, 3, len(grouped["true"])) 239 | assert.Equal(t, 3, len(grouped["false"])) 240 | 241 | } 242 | 243 | func TestReplace{4}(t *testing.T) { 244 | 245 | v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} 246 | 247 | rawArr := v.Must{4}Slice() 248 | 249 | replaced := v.Replace{4}(func(index int, val {1}) {1} { 250 | if index < len(rawArr)-1 { 251 | return rawArr[index+1] 252 | } 253 | return rawArr[0] 254 | }) 255 | 256 | replacedArr := replaced.Must{4}Slice() 257 | if assert.Equal(t, 6, len(replacedArr)) { 258 | assert.Equal(t, replacedArr[0], rawArr[1]) 259 | assert.Equal(t, replacedArr[1], rawArr[2]) 260 | assert.Equal(t, replacedArr[2], rawArr[3]) 261 | assert.Equal(t, replacedArr[3], rawArr[4]) 262 | assert.Equal(t, replacedArr[4], rawArr[5]) 263 | assert.Equal(t, replacedArr[5], rawArr[0]) 264 | } 265 | 266 | } 267 | 268 | func TestCollect{4}(t *testing.T) { 269 | 270 | v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }} 271 | 272 | collected := v.Collect{4}(func(index int, val {1}) interface{} { 273 | return index 274 | }) 275 | 276 | collectedArr := collected.MustInterSlice() 277 | if assert.Equal(t, 6, len(collectedArr)) { 278 | assert.Equal(t, collectedArr[0], 0) 279 | assert.Equal(t, collectedArr[1], 1) 280 | assert.Equal(t, collectedArr[2], 2) 281 | assert.Equal(t, collectedArr[3], 3) 282 | assert.Equal(t, collectedArr[4], 4) 283 | assert.Equal(t, collectedArr[5], 5) 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/codegen/types_list.txt: -------------------------------------------------------------------------------- 1 | Interface,interface{},"something",nil,Inter 2 | Map,map[string]interface{},map[string]interface{}{"name":"Tyler"},nil,MSI 3 | ObjxMap,(Map),New(1),New(nil),ObjxMap 4 | Bool,bool,true,false,Bool 5 | String,string,"hello","",Str 6 | Int,int,1,0,Int 7 | Int8,int8,1,0,Int8 8 | Int16,int16,1,0,Int16 9 | Int32,int32,1,0,Int32 10 | Int64,int64,1,0,Int64 11 | Uint,uint,1,0,Uint 12 | Uint8,uint8,1,0,Uint8 13 | Uint16,uint16,1,0,Uint16 14 | Uint32,uint32,1,0,Uint32 15 | Uint64,uint64,1,0,Uint64 16 | Uintptr,uintptr,1,0,Uintptr 17 | Float32,float32,1,0,Float32 18 | Float64,float64,1,0,Float64 19 | Complex64,complex64,1,0,Complex64 20 | Complex128,complex128,1,0,Complex128 21 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/constants.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | const ( 4 | // PathSeparator is the character used to separate the elements 5 | // of the keypath. 6 | // 7 | // For example, `location.address.city` 8 | PathSeparator string = "." 9 | 10 | // SignatureSeparator is the character that is used to 11 | // separate the Base64 string from the security signature. 12 | SignatureSeparator = "_" 13 | ) 14 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/conversions.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "net/url" 10 | ) 11 | 12 | // JSON converts the contained object to a JSON string 13 | // representation 14 | func (m Map) JSON() (string, error) { 15 | 16 | result, err := json.Marshal(m) 17 | 18 | if err != nil { 19 | err = errors.New("objx: JSON encode failed with: " + err.Error()) 20 | } 21 | 22 | return string(result), err 23 | 24 | } 25 | 26 | // MustJSON converts the contained object to a JSON string 27 | // representation and panics if there is an error 28 | func (m Map) MustJSON() string { 29 | result, err := m.JSON() 30 | if err != nil { 31 | panic(err.Error()) 32 | } 33 | return result 34 | } 35 | 36 | // Base64 converts the contained object to a Base64 string 37 | // representation of the JSON string representation 38 | func (m Map) Base64() (string, error) { 39 | 40 | var buf bytes.Buffer 41 | 42 | jsonData, err := m.JSON() 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | encoder := base64.NewEncoder(base64.StdEncoding, &buf) 48 | encoder.Write([]byte(jsonData)) 49 | encoder.Close() 50 | 51 | return buf.String(), nil 52 | 53 | } 54 | 55 | // MustBase64 converts the contained object to a Base64 string 56 | // representation of the JSON string representation and panics 57 | // if there is an error 58 | func (m Map) MustBase64() string { 59 | result, err := m.Base64() 60 | if err != nil { 61 | panic(err.Error()) 62 | } 63 | return result 64 | } 65 | 66 | // SignedBase64 converts the contained object to a Base64 string 67 | // representation of the JSON string representation and signs it 68 | // using the provided key. 69 | func (m Map) SignedBase64(key string) (string, error) { 70 | 71 | base64, err := m.Base64() 72 | if err != nil { 73 | return "", err 74 | } 75 | 76 | sig := HashWithKey(base64, key) 77 | 78 | return base64 + SignatureSeparator + sig, nil 79 | 80 | } 81 | 82 | // MustSignedBase64 converts the contained object to a Base64 string 83 | // representation of the JSON string representation and signs it 84 | // using the provided key and panics if there is an error 85 | func (m Map) MustSignedBase64(key string) string { 86 | result, err := m.SignedBase64(key) 87 | if err != nil { 88 | panic(err.Error()) 89 | } 90 | return result 91 | } 92 | 93 | /* 94 | URL Query 95 | ------------------------------------------------ 96 | */ 97 | 98 | // URLValues creates a url.Values object from an Obj. This 99 | // function requires that the wrapped object be a map[string]interface{} 100 | func (m Map) URLValues() url.Values { 101 | 102 | vals := make(url.Values) 103 | 104 | for k, v := range m { 105 | //TODO: can this be done without sprintf? 106 | vals.Set(k, fmt.Sprintf("%v", v)) 107 | } 108 | 109 | return vals 110 | } 111 | 112 | // URLQuery gets an encoded URL query representing the given 113 | // Obj. This function requires that the wrapped object be a 114 | // map[string]interface{} 115 | func (m Map) URLQuery() (string, error) { 116 | return m.URLValues().Encode(), nil 117 | } 118 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/conversions_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestConversionJSON(t *testing.T) { 9 | 10 | jsonString := `{"name":"Mat"}` 11 | o := MustFromJSON(jsonString) 12 | 13 | result, err := o.JSON() 14 | 15 | if assert.NoError(t, err) { 16 | assert.Equal(t, jsonString, result) 17 | } 18 | 19 | assert.Equal(t, jsonString, o.MustJSON()) 20 | 21 | } 22 | 23 | func TestConversionJSONWithError(t *testing.T) { 24 | 25 | o := MSI() 26 | o["test"] = func() {} 27 | 28 | assert.Panics(t, func() { 29 | o.MustJSON() 30 | }) 31 | 32 | _, err := o.JSON() 33 | 34 | assert.Error(t, err) 35 | 36 | } 37 | 38 | func TestConversionBase64(t *testing.T) { 39 | 40 | o := New(map[string]interface{}{"name": "Mat"}) 41 | 42 | result, err := o.Base64() 43 | 44 | if assert.NoError(t, err) { 45 | assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", result) 46 | } 47 | 48 | assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", o.MustBase64()) 49 | 50 | } 51 | 52 | func TestConversionBase64WithError(t *testing.T) { 53 | 54 | o := MSI() 55 | o["test"] = func() {} 56 | 57 | assert.Panics(t, func() { 58 | o.MustBase64() 59 | }) 60 | 61 | _, err := o.Base64() 62 | 63 | assert.Error(t, err) 64 | 65 | } 66 | 67 | func TestConversionSignedBase64(t *testing.T) { 68 | 69 | o := New(map[string]interface{}{"name": "Mat"}) 70 | 71 | result, err := o.SignedBase64("key") 72 | 73 | if assert.NoError(t, err) { 74 | assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", result) 75 | } 76 | 77 | assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", o.MustSignedBase64("key")) 78 | 79 | } 80 | 81 | func TestConversionSignedBase64WithError(t *testing.T) { 82 | 83 | o := MSI() 84 | o["test"] = func() {} 85 | 86 | assert.Panics(t, func() { 87 | o.MustSignedBase64("key") 88 | }) 89 | 90 | _, err := o.SignedBase64("key") 91 | 92 | assert.Error(t, err) 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/doc.go: -------------------------------------------------------------------------------- 1 | // objx - Go package for dealing with maps, slices, JSON and other data. 2 | // 3 | // Overview 4 | // 5 | // Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes 6 | // a powerful `Get` method (among others) that allows you to easily and quickly get 7 | // access to data within the map, without having to worry too much about type assertions, 8 | // missing data, default values etc. 9 | // 10 | // Pattern 11 | // 12 | // Objx uses a preditable pattern to make access data from within `map[string]interface{}'s 13 | // easy. 14 | // 15 | // Call one of the `objx.` functions to create your `objx.Map` to get going: 16 | // 17 | // m, err := objx.FromJSON(json) 18 | // 19 | // NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, 20 | // the rest will be optimistic and try to figure things out without panicking. 21 | // 22 | // Use `Get` to access the value you're interested in. You can use dot and array 23 | // notation too: 24 | // 25 | // m.Get("places[0].latlng") 26 | // 27 | // Once you have saught the `Value` you're interested in, you can use the `Is*` methods 28 | // to determine its type. 29 | // 30 | // if m.Get("code").IsStr() { /* ... */ } 31 | // 32 | // Or you can just assume the type, and use one of the strong type methods to 33 | // extract the real value: 34 | // 35 | // m.Get("code").Int() 36 | // 37 | // If there's no value there (or if it's the wrong type) then a default value 38 | // will be returned, or you can be explicit about the default value. 39 | // 40 | // Get("code").Int(-1) 41 | // 42 | // If you're dealing with a slice of data as a value, Objx provides many useful 43 | // methods for iterating, manipulating and selecting that data. You can find out more 44 | // by exploring the index below. 45 | // 46 | // Reading data 47 | // 48 | // A simple example of how to use Objx: 49 | // 50 | // // use MustFromJSON to make an objx.Map from some JSON 51 | // m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) 52 | // 53 | // // get the details 54 | // name := m.Get("name").Str() 55 | // age := m.Get("age").Int() 56 | // 57 | // // get their nickname (or use their name if they 58 | // // don't have one) 59 | // nickname := m.Get("nickname").Str(name) 60 | // 61 | // Ranging 62 | // 63 | // Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For 64 | // example, to `range` the data, do what you would expect: 65 | // 66 | // m := objx.MustFromJSON(json) 67 | // for key, value := range m { 68 | // 69 | // /* ... do your magic ... */ 70 | // 71 | // } 72 | package objx 73 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/fixture_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var fixtures = []struct { 9 | // name is the name of the fixture (used for reporting 10 | // failures) 11 | name string 12 | // data is the JSON data to be worked on 13 | data string 14 | // get is the argument(s) to pass to Get 15 | get interface{} 16 | // output is the expected output 17 | output interface{} 18 | }{ 19 | { 20 | name: "Simple get", 21 | data: `{"name": "Mat"}`, 22 | get: "name", 23 | output: "Mat", 24 | }, 25 | { 26 | name: "Get with dot notation", 27 | data: `{"address": {"city": "Boulder"}}`, 28 | get: "address.city", 29 | output: "Boulder", 30 | }, 31 | { 32 | name: "Deep get with dot notation", 33 | data: `{"one": {"two": {"three": {"four": "hello"}}}}`, 34 | get: "one.two.three.four", 35 | output: "hello", 36 | }, 37 | { 38 | name: "Get missing with dot notation", 39 | data: `{"one": {"two": {"three": {"four": "hello"}}}}`, 40 | get: "one.ten", 41 | output: nil, 42 | }, 43 | { 44 | name: "Get with array notation", 45 | data: `{"tags": ["one", "two", "three"]}`, 46 | get: "tags[1]", 47 | output: "two", 48 | }, 49 | { 50 | name: "Get with array and dot notation", 51 | data: `{"types": { "tags": ["one", "two", "three"]}}`, 52 | get: "types.tags[1]", 53 | output: "two", 54 | }, 55 | { 56 | name: "Get with array and dot notation - field after array", 57 | data: `{"tags": [{"name":"one"}, {"name":"two"}, {"name":"three"}]}`, 58 | get: "tags[1].name", 59 | output: "two", 60 | }, 61 | { 62 | name: "Complex get with array and dot notation", 63 | data: `{"tags": [{"list": [{"one":"pizza"}]}]}`, 64 | get: "tags[0].list[0].one", 65 | output: "pizza", 66 | }, 67 | { 68 | name: "Get field from within string should be nil", 69 | data: `{"name":"Tyler"}`, 70 | get: "name.something", 71 | output: nil, 72 | }, 73 | { 74 | name: "Get field from within string (using array accessor) should be nil", 75 | data: `{"numbers":["one", "two", "three"]}`, 76 | get: "numbers[0].nope", 77 | output: nil, 78 | }, 79 | } 80 | 81 | func TestFixtures(t *testing.T) { 82 | 83 | for _, fixture := range fixtures { 84 | 85 | m := MustFromJSON(fixture.data) 86 | 87 | // get the value 88 | t.Logf("Running get fixture: \"%s\" (%v)", fixture.name, fixture) 89 | value := m.Get(fixture.get.(string)) 90 | 91 | // make sure it matches 92 | assert.Equal(t, fixture.output, value.data, 93 | "Get fixture \"%s\" failed: %v", fixture.name, fixture, 94 | ) 95 | 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/map.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // MSIConvertable is an interface that defines methods for converting your 13 | // custom types to a map[string]interface{} representation. 14 | type MSIConvertable interface { 15 | // MSI gets a map[string]interface{} (msi) representing the 16 | // object. 17 | MSI() map[string]interface{} 18 | } 19 | 20 | // Map provides extended functionality for working with 21 | // untyped data, in particular map[string]interface (msi). 22 | type Map map[string]interface{} 23 | 24 | // Value returns the internal value instance 25 | func (m Map) Value() *Value { 26 | return &Value{data: m} 27 | } 28 | 29 | // Nil represents a nil Map. 30 | var Nil Map = New(nil) 31 | 32 | // New creates a new Map containing the map[string]interface{} in the data argument. 33 | // If the data argument is not a map[string]interface, New attempts to call the 34 | // MSI() method on the MSIConvertable interface to create one. 35 | func New(data interface{}) Map { 36 | if _, ok := data.(map[string]interface{}); !ok { 37 | if converter, ok := data.(MSIConvertable); ok { 38 | data = converter.MSI() 39 | } else { 40 | return nil 41 | } 42 | } 43 | return Map(data.(map[string]interface{})) 44 | } 45 | 46 | // MSI creates a map[string]interface{} and puts it inside a new Map. 47 | // 48 | // The arguments follow a key, value pattern. 49 | // 50 | // Panics 51 | // 52 | // Panics if any key arugment is non-string or if there are an odd number of arguments. 53 | // 54 | // Example 55 | // 56 | // To easily create Maps: 57 | // 58 | // m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) 59 | // 60 | // // creates an Map equivalent to 61 | // m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}}) 62 | func MSI(keyAndValuePairs ...interface{}) Map { 63 | 64 | newMap := make(map[string]interface{}) 65 | keyAndValuePairsLen := len(keyAndValuePairs) 66 | 67 | if keyAndValuePairsLen%2 != 0 { 68 | panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.") 69 | } 70 | 71 | for i := 0; i < keyAndValuePairsLen; i = i + 2 { 72 | 73 | key := keyAndValuePairs[i] 74 | value := keyAndValuePairs[i+1] 75 | 76 | // make sure the key is a string 77 | keyString, keyStringOK := key.(string) 78 | if !keyStringOK { 79 | panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.") 80 | } 81 | 82 | newMap[keyString] = value 83 | 84 | } 85 | 86 | return New(newMap) 87 | } 88 | 89 | // ****** Conversion Constructors 90 | 91 | // MustFromJSON creates a new Map containing the data specified in the 92 | // jsonString. 93 | // 94 | // Panics if the JSON is invalid. 95 | func MustFromJSON(jsonString string) Map { 96 | o, err := FromJSON(jsonString) 97 | 98 | if err != nil { 99 | panic("objx: MustFromJSON failed with error: " + err.Error()) 100 | } 101 | 102 | return o 103 | } 104 | 105 | // FromJSON creates a new Map containing the data specified in the 106 | // jsonString. 107 | // 108 | // Returns an error if the JSON is invalid. 109 | func FromJSON(jsonString string) (Map, error) { 110 | 111 | var data interface{} 112 | err := json.Unmarshal([]byte(jsonString), &data) 113 | 114 | if err != nil { 115 | return Nil, err 116 | } 117 | 118 | return New(data), nil 119 | 120 | } 121 | 122 | // FromBase64 creates a new Obj containing the data specified 123 | // in the Base64 string. 124 | // 125 | // The string is an encoded JSON string returned by Base64 126 | func FromBase64(base64String string) (Map, error) { 127 | 128 | decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) 129 | 130 | decoded, err := ioutil.ReadAll(decoder) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | return FromJSON(string(decoded)) 136 | } 137 | 138 | // MustFromBase64 creates a new Obj containing the data specified 139 | // in the Base64 string and panics if there is an error. 140 | // 141 | // The string is an encoded JSON string returned by Base64 142 | func MustFromBase64(base64String string) Map { 143 | 144 | result, err := FromBase64(base64String) 145 | 146 | if err != nil { 147 | panic("objx: MustFromBase64 failed with error: " + err.Error()) 148 | } 149 | 150 | return result 151 | } 152 | 153 | // FromSignedBase64 creates a new Obj containing the data specified 154 | // in the Base64 string. 155 | // 156 | // The string is an encoded JSON string returned by SignedBase64 157 | func FromSignedBase64(base64String, key string) (Map, error) { 158 | parts := strings.Split(base64String, SignatureSeparator) 159 | if len(parts) != 2 { 160 | return nil, errors.New("objx: Signed base64 string is malformed.") 161 | } 162 | 163 | sig := HashWithKey(parts[0], key) 164 | if parts[1] != sig { 165 | return nil, errors.New("objx: Signature for base64 data does not match.") 166 | } 167 | 168 | return FromBase64(parts[0]) 169 | } 170 | 171 | // MustFromSignedBase64 creates a new Obj containing the data specified 172 | // in the Base64 string and panics if there is an error. 173 | // 174 | // The string is an encoded JSON string returned by Base64 175 | func MustFromSignedBase64(base64String, key string) Map { 176 | 177 | result, err := FromSignedBase64(base64String, key) 178 | 179 | if err != nil { 180 | panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) 181 | } 182 | 183 | return result 184 | } 185 | 186 | // FromURLQuery generates a new Obj by parsing the specified 187 | // query. 188 | // 189 | // For queries with multiple values, the first value is selected. 190 | func FromURLQuery(query string) (Map, error) { 191 | 192 | vals, err := url.ParseQuery(query) 193 | 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | m := make(map[string]interface{}) 199 | for k, vals := range vals { 200 | m[k] = vals[0] 201 | } 202 | 203 | return New(m), nil 204 | } 205 | 206 | // MustFromURLQuery generates a new Obj by parsing the specified 207 | // query. 208 | // 209 | // For queries with multiple values, the first value is selected. 210 | // 211 | // Panics if it encounters an error 212 | func MustFromURLQuery(query string) Map { 213 | 214 | o, err := FromURLQuery(query) 215 | 216 | if err != nil { 217 | panic("objx: MustFromURLQuery failed with error: " + err.Error()) 218 | } 219 | 220 | return o 221 | 222 | } 223 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/map_for_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | var TestMap map[string]interface{} = map[string]interface{}{ 4 | "name": "Tyler", 5 | "address": map[string]interface{}{ 6 | "city": "Salt Lake City", 7 | "state": "UT", 8 | }, 9 | "numbers": []interface{}{"one", "two", "three", "four", "five"}, 10 | } 11 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/map_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | type Convertable struct { 9 | name string 10 | } 11 | 12 | func (c *Convertable) MSI() map[string]interface{} { 13 | return map[string]interface{}{"name": c.name} 14 | } 15 | 16 | type Unconvertable struct { 17 | name string 18 | } 19 | 20 | func TestMapCreation(t *testing.T) { 21 | 22 | o := New(nil) 23 | assert.Nil(t, o) 24 | 25 | o = New("Tyler") 26 | assert.Nil(t, o) 27 | 28 | unconvertable := &Unconvertable{name: "Tyler"} 29 | o = New(unconvertable) 30 | assert.Nil(t, o) 31 | 32 | convertable := &Convertable{name: "Tyler"} 33 | o = New(convertable) 34 | if assert.NotNil(t, convertable) { 35 | assert.Equal(t, "Tyler", o["name"], "Tyler") 36 | } 37 | 38 | o = MSI() 39 | if assert.NotNil(t, o) { 40 | assert.NotNil(t, o) 41 | } 42 | 43 | o = MSI("name", "Tyler") 44 | if assert.NotNil(t, o) { 45 | if assert.NotNil(t, o) { 46 | assert.Equal(t, o["name"], "Tyler") 47 | } 48 | } 49 | 50 | } 51 | 52 | func TestMapMustFromJSONWithError(t *testing.T) { 53 | 54 | _, err := FromJSON(`"name":"Mat"}`) 55 | assert.Error(t, err) 56 | 57 | } 58 | 59 | func TestMapFromJSON(t *testing.T) { 60 | 61 | o := MustFromJSON(`{"name":"Mat"}`) 62 | 63 | if assert.NotNil(t, o) { 64 | if assert.NotNil(t, o) { 65 | assert.Equal(t, "Mat", o["name"]) 66 | } 67 | } 68 | 69 | } 70 | 71 | func TestMapFromJSONWithError(t *testing.T) { 72 | 73 | var m Map 74 | 75 | assert.Panics(t, func() { 76 | m = MustFromJSON(`"name":"Mat"}`) 77 | }) 78 | 79 | assert.Nil(t, m) 80 | 81 | } 82 | 83 | func TestMapFromBase64String(t *testing.T) { 84 | 85 | base64String := "eyJuYW1lIjoiTWF0In0=" 86 | 87 | o, err := FromBase64(base64String) 88 | 89 | if assert.NoError(t, err) { 90 | assert.Equal(t, o.Get("name").Str(), "Mat") 91 | } 92 | 93 | assert.Equal(t, MustFromBase64(base64String).Get("name").Str(), "Mat") 94 | 95 | } 96 | 97 | func TestMapFromBase64StringWithError(t *testing.T) { 98 | 99 | base64String := "eyJuYW1lIjoiTWFasd0In0=" 100 | 101 | _, err := FromBase64(base64String) 102 | 103 | assert.Error(t, err) 104 | 105 | assert.Panics(t, func() { 106 | MustFromBase64(base64String) 107 | }) 108 | 109 | } 110 | 111 | func TestMapFromSignedBase64String(t *testing.T) { 112 | 113 | base64String := "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6" 114 | 115 | o, err := FromSignedBase64(base64String, "key") 116 | 117 | if assert.NoError(t, err) { 118 | assert.Equal(t, o.Get("name").Str(), "Mat") 119 | } 120 | 121 | assert.Equal(t, MustFromSignedBase64(base64String, "key").Get("name").Str(), "Mat") 122 | 123 | } 124 | 125 | func TestMapFromSignedBase64StringWithError(t *testing.T) { 126 | 127 | base64String := "eyJuYW1lasdIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6" 128 | 129 | _, err := FromSignedBase64(base64String, "key") 130 | 131 | assert.Error(t, err) 132 | 133 | assert.Panics(t, func() { 134 | MustFromSignedBase64(base64String, "key") 135 | }) 136 | 137 | } 138 | 139 | func TestMapFromURLQuery(t *testing.T) { 140 | 141 | m, err := FromURLQuery("name=tyler&state=UT") 142 | if assert.NoError(t, err) && assert.NotNil(t, m) { 143 | assert.Equal(t, "tyler", m.Get("name").Str()) 144 | assert.Equal(t, "UT", m.Get("state").Str()) 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/mutations.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | // Exclude returns a new Map with the keys in the specified []string 4 | // excluded. 5 | func (d Map) Exclude(exclude []string) Map { 6 | 7 | excluded := make(Map) 8 | for k, v := range d { 9 | var shouldInclude bool = true 10 | for _, toExclude := range exclude { 11 | if k == toExclude { 12 | shouldInclude = false 13 | break 14 | } 15 | } 16 | if shouldInclude { 17 | excluded[k] = v 18 | } 19 | } 20 | 21 | return excluded 22 | } 23 | 24 | // Copy creates a shallow copy of the Obj. 25 | func (m Map) Copy() Map { 26 | copied := make(map[string]interface{}) 27 | for k, v := range m { 28 | copied[k] = v 29 | } 30 | return New(copied) 31 | } 32 | 33 | // Merge blends the specified map with a copy of this map and returns the result. 34 | // 35 | // Keys that appear in both will be selected from the specified map. 36 | // This method requires that the wrapped object be a map[string]interface{} 37 | func (m Map) Merge(merge Map) Map { 38 | return m.Copy().MergeHere(merge) 39 | } 40 | 41 | // Merge blends the specified map with this map and returns the current map. 42 | // 43 | // Keys that appear in both will be selected from the specified map. The original map 44 | // will be modified. This method requires that 45 | // the wrapped object be a map[string]interface{} 46 | func (m Map) MergeHere(merge Map) Map { 47 | 48 | for k, v := range merge { 49 | m[k] = v 50 | } 51 | 52 | return m 53 | 54 | } 55 | 56 | // Transform builds a new Obj giving the transformer a chance 57 | // to change the keys and values as it goes. This method requires that 58 | // the wrapped object be a map[string]interface{} 59 | func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { 60 | newMap := make(map[string]interface{}) 61 | for k, v := range m { 62 | modifiedKey, modifiedVal := transformer(k, v) 63 | newMap[modifiedKey] = modifiedVal 64 | } 65 | return New(newMap) 66 | } 67 | 68 | // TransformKeys builds a new map using the specified key mapping. 69 | // 70 | // Unspecified keys will be unaltered. 71 | // This method requires that the wrapped object be a map[string]interface{} 72 | func (m Map) TransformKeys(mapping map[string]string) Map { 73 | return m.Transform(func(key string, value interface{}) (string, interface{}) { 74 | 75 | if newKey, ok := mapping[key]; ok { 76 | return newKey, value 77 | } 78 | 79 | return key, value 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/mutations_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestExclude(t *testing.T) { 9 | 10 | d := make(Map) 11 | d["name"] = "Mat" 12 | d["age"] = 29 13 | d["secret"] = "ABC" 14 | 15 | excluded := d.Exclude([]string{"secret"}) 16 | 17 | assert.Equal(t, d["name"], excluded["name"]) 18 | assert.Equal(t, d["age"], excluded["age"]) 19 | assert.False(t, excluded.Has("secret"), "secret should be excluded") 20 | 21 | } 22 | 23 | func TestCopy(t *testing.T) { 24 | 25 | d1 := make(map[string]interface{}) 26 | d1["name"] = "Tyler" 27 | d1["location"] = "UT" 28 | 29 | d1Obj := New(d1) 30 | d2Obj := d1Obj.Copy() 31 | 32 | d2Obj["name"] = "Mat" 33 | 34 | assert.Equal(t, d1Obj.Get("name").Str(), "Tyler") 35 | assert.Equal(t, d2Obj.Get("name").Str(), "Mat") 36 | 37 | } 38 | 39 | func TestMerge(t *testing.T) { 40 | 41 | d := make(map[string]interface{}) 42 | d["name"] = "Mat" 43 | 44 | d1 := make(map[string]interface{}) 45 | d1["name"] = "Tyler" 46 | d1["location"] = "UT" 47 | 48 | dObj := New(d) 49 | d1Obj := New(d1) 50 | 51 | merged := dObj.Merge(d1Obj) 52 | 53 | assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str()) 54 | assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str()) 55 | assert.Empty(t, dObj.Get("location").Str()) 56 | 57 | } 58 | 59 | func TestMergeHere(t *testing.T) { 60 | 61 | d := make(map[string]interface{}) 62 | d["name"] = "Mat" 63 | 64 | d1 := make(map[string]interface{}) 65 | d1["name"] = "Tyler" 66 | d1["location"] = "UT" 67 | 68 | dObj := New(d) 69 | d1Obj := New(d1) 70 | 71 | merged := dObj.MergeHere(d1Obj) 72 | 73 | assert.Equal(t, dObj, merged, "With MergeHere, it should return the first modified map") 74 | assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str()) 75 | assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str()) 76 | assert.Equal(t, merged.Get("location").Str(), dObj.Get("location").Str()) 77 | } 78 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/security.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | ) 7 | 8 | // HashWithKey hashes the specified string using the security 9 | // key. 10 | func HashWithKey(data, key string) string { 11 | hash := sha1.New() 12 | hash.Write([]byte(data + ":" + key)) 13 | return hex.EncodeToString(hash.Sum(nil)) 14 | } 15 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/security_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestHashWithKey(t *testing.T) { 9 | 10 | assert.Equal(t, "0ce84d8d01f2c7b6e0882b784429c54d280ea2d9", HashWithKey("abc", "def")) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/simple_example_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestSimpleExample(t *testing.T) { 9 | 10 | // build a map from a JSON object 11 | o := MustFromJSON(`{"name":"Mat","foods":["indian","chinese"], "location":{"county":"hobbiton","city":"the shire"}}`) 12 | 13 | // Map can be used as a straight map[string]interface{} 14 | assert.Equal(t, o["name"], "Mat") 15 | 16 | // Get an Value object 17 | v := o.Get("name") 18 | assert.Equal(t, v, &Value{data: "Mat"}) 19 | 20 | // Test the contained value 21 | assert.False(t, v.IsInt()) 22 | assert.False(t, v.IsBool()) 23 | assert.True(t, v.IsStr()) 24 | 25 | // Get the contained value 26 | assert.Equal(t, v.Str(), "Mat") 27 | 28 | // Get a default value if the contained value is not of the expected type or does not exist 29 | assert.Equal(t, 1, v.Int(1)) 30 | 31 | // Get a value by using array notation 32 | assert.Equal(t, "indian", o.Get("foods[0]").Data()) 33 | 34 | // Set a value by using array notation 35 | o.Set("foods[0]", "italian") 36 | assert.Equal(t, "italian", o.Get("foods[0]").Str()) 37 | 38 | // Get a value by using dot notation 39 | assert.Equal(t, "hobbiton", o.Get("location.county").Str()) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/tests.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | // Has gets whether there is something at the specified selector 4 | // or not. 5 | // 6 | // If m is nil, Has will always return false. 7 | func (m Map) Has(selector string) bool { 8 | if m == nil { 9 | return false 10 | } 11 | return !m.Get(selector).IsNil() 12 | } 13 | 14 | // IsNil gets whether the data is nil or not. 15 | func (v *Value) IsNil() bool { 16 | return v == nil || v.data == nil 17 | } 18 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/tests_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestHas(t *testing.T) { 9 | 10 | m := New(TestMap) 11 | 12 | assert.True(t, m.Has("name")) 13 | assert.True(t, m.Has("address.state")) 14 | assert.True(t, m.Has("numbers[4]")) 15 | 16 | assert.False(t, m.Has("address.state.nope")) 17 | assert.False(t, m.Has("address.nope")) 18 | assert.False(t, m.Has("nope")) 19 | assert.False(t, m.Has("numbers[5]")) 20 | 21 | m = nil 22 | assert.False(t, m.Has("nothing")) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/value.go: -------------------------------------------------------------------------------- 1 | package objx 2 | 3 | // Value provides methods for extracting interface{} data in various 4 | // types. 5 | type Value struct { 6 | // data contains the raw data being managed by this Value 7 | data interface{} 8 | } 9 | 10 | // Data returns the raw data contained by this Value 11 | func (v *Value) Data() interface{} { 12 | return v.data 13 | } 14 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/objx/value_test.go: -------------------------------------------------------------------------------- 1 | package objx 2 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/testify/assert/doc.go: -------------------------------------------------------------------------------- 1 | // A set of comprehensive testing tools for use with the normal Go testing system. 2 | // 3 | // Example Usage 4 | // 5 | // The following is a complete example using assert in a standard test function: 6 | // import ( 7 | // "testing" 8 | // "github.com/stretchr/testify/assert" 9 | // ) 10 | // 11 | // func TestSomething(t *testing.T) { 12 | // 13 | // var a string = "Hello" 14 | // var b string = "Hello" 15 | // 16 | // assert.Equal(t, a, b, "The two words should be the same.") 17 | // 18 | // } 19 | // 20 | // if you assert many times, use the below: 21 | // 22 | // import ( 23 | // "testing" 24 | // "github.com/stretchr/testify/assert" 25 | // ) 26 | // 27 | // func TestSomething(t *testing.T) { 28 | // assert := assert.New(t) 29 | // 30 | // var a string = "Hello" 31 | // var b string = "Hello" 32 | // 33 | // assert.Equal(a, b, "The two words should be the same.") 34 | // } 35 | // 36 | // Assertions 37 | // 38 | // Assertions allow you to easily write test code, and are global funcs in the `assert` package. 39 | // All assertion functions take, as the first argument, the `*testing.T` object provided by the 40 | // testing framework. This allows the assertion funcs to write the failings and other details to 41 | // the correct place. 42 | // 43 | // Every assertion function also takes an optional string message as the final argument, 44 | // allowing custom error messages to be appended to the message the assertion method outputs. 45 | // 46 | // Here is an overview of the assert functions: 47 | // 48 | // assert.Equal(t, expected, actual [, message [, format-args]) 49 | // 50 | // assert.NotEqual(t, notExpected, actual [, message [, format-args]]) 51 | // 52 | // assert.True(t, actualBool [, message [, format-args]]) 53 | // 54 | // assert.False(t, actualBool [, message [, format-args]]) 55 | // 56 | // assert.Nil(t, actualObject [, message [, format-args]]) 57 | // 58 | // assert.NotNil(t, actualObject [, message [, format-args]]) 59 | // 60 | // assert.Empty(t, actualObject [, message [, format-args]]) 61 | // 62 | // assert.NotEmpty(t, actualObject [, message [, format-args]]) 63 | // 64 | // assert.Len(t, actualObject, expectedLength, [, message [, format-args]]) 65 | // 66 | // assert.Error(t, errorObject [, message [, format-args]]) 67 | // 68 | // assert.NoError(t, errorObject [, message [, format-args]]) 69 | // 70 | // assert.EqualError(t, theError, errString [, message [, format-args]]) 71 | // 72 | // assert.Implements(t, (*MyInterface)(nil), new(MyObject) [,message [, format-args]]) 73 | // 74 | // assert.IsType(t, expectedObject, actualObject [, message [, format-args]]) 75 | // 76 | // assert.Contains(t, stringOrSlice, substringOrElement [, message [, format-args]]) 77 | // 78 | // assert.NotContains(t, stringOrSlice, substringOrElement [, message [, format-args]]) 79 | // 80 | // assert.Panics(t, func(){ 81 | // 82 | // // call code that should panic 83 | // 84 | // } [, message [, format-args]]) 85 | // 86 | // assert.NotPanics(t, func(){ 87 | // 88 | // // call code that should not panic 89 | // 90 | // } [, message [, format-args]]) 91 | // 92 | // assert.WithinDuration(t, timeA, timeB, deltaTime, [, message [, format-args]]) 93 | // 94 | // assert.InDelta(t, numA, numB, delta, [, message [, format-args]]) 95 | // 96 | // assert.InEpsilon(t, numA, numB, epsilon, [, message [, format-args]]) 97 | // 98 | // assert package contains Assertions object. it has assertion methods. 99 | // 100 | // Here is an overview of the assert functions: 101 | // assert.Equal(expected, actual [, message [, format-args]) 102 | // 103 | // assert.NotEqual(notExpected, actual [, message [, format-args]]) 104 | // 105 | // assert.True(actualBool [, message [, format-args]]) 106 | // 107 | // assert.False(actualBool [, message [, format-args]]) 108 | // 109 | // assert.Nil(actualObject [, message [, format-args]]) 110 | // 111 | // assert.NotNil(actualObject [, message [, format-args]]) 112 | // 113 | // assert.Empty(actualObject [, message [, format-args]]) 114 | // 115 | // assert.NotEmpty(actualObject [, message [, format-args]]) 116 | // 117 | // assert.Len(actualObject, expectedLength, [, message [, format-args]]) 118 | // 119 | // assert.Error(errorObject [, message [, format-args]]) 120 | // 121 | // assert.NoError(errorObject [, message [, format-args]]) 122 | // 123 | // assert.EqualError(theError, errString [, message [, format-args]]) 124 | // 125 | // assert.Implements((*MyInterface)(nil), new(MyObject) [,message [, format-args]]) 126 | // 127 | // assert.IsType(expectedObject, actualObject [, message [, format-args]]) 128 | // 129 | // assert.Contains(stringOrSlice, substringOrElement [, message [, format-args]]) 130 | // 131 | // assert.NotContains(stringOrSlice, substringOrElement [, message [, format-args]]) 132 | // 133 | // assert.Panics(func(){ 134 | // 135 | // // call code that should panic 136 | // 137 | // } [, message [, format-args]]) 138 | // 139 | // assert.NotPanics(func(){ 140 | // 141 | // // call code that should not panic 142 | // 143 | // } [, message [, format-args]]) 144 | // 145 | // assert.WithinDuration(timeA, timeB, deltaTime, [, message [, format-args]]) 146 | // 147 | // assert.InDelta(numA, numB, delta, [, message [, format-args]]) 148 | // 149 | // assert.InEpsilon(numA, numB, epsilon, [, message [, format-args]]) 150 | package assert 151 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/testify/assert/errors.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // AnError is an error instance useful for testing. If the code does not care 8 | // about error specifics, and only needs to return the error for example, this 9 | // error should be used to make the test code more readable. 10 | var AnError = errors.New("assert.AnError general error for testing") 11 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // httpCode is a helper that returns HTTP code of the response. It returns -1 12 | // if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, mode, url string, values url.Values) int { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(mode, url+"?"+values.Encode(), nil) 16 | if err != nil { 17 | return -1 18 | } 19 | handler(w, req) 20 | return w.Code 21 | } 22 | 23 | // HTTPSuccess asserts that a specified handler returns a success status code. 24 | // 25 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 26 | // 27 | // Returns whether the assertion was successful (true) or not (false). 28 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, mode, url string, values url.Values) bool { 29 | code := httpCode(handler, mode, url, values) 30 | if code == -1 { 31 | return false 32 | } 33 | return code >= http.StatusOK && code <= http.StatusPartialContent 34 | } 35 | 36 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 37 | // 38 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 39 | // 40 | // Returns whether the assertion was successful (true) or not (false). 41 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, mode, url string, values url.Values) bool { 42 | code := httpCode(handler, mode, url, values) 43 | if code == -1 { 44 | return false 45 | } 46 | return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 47 | } 48 | 49 | // HTTPError asserts that a specified handler returns an error status code. 50 | // 51 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 52 | // 53 | // Returns whether the assertion was successful (true) or not (false). 54 | func HTTPError(t TestingT, handler http.HandlerFunc, mode, url string, values url.Values) bool { 55 | code := httpCode(handler, mode, url, values) 56 | if code == -1 { 57 | return false 58 | } 59 | return code >= http.StatusBadRequest 60 | } 61 | 62 | // HttpBody is a helper that returns HTTP body of the response. It returns 63 | // empty string if building a new request fails. 64 | func HttpBody(handler http.HandlerFunc, mode, url string, values url.Values) string { 65 | w := httptest.NewRecorder() 66 | req, err := http.NewRequest(mode, url+"?"+values.Encode(), nil) 67 | if err != nil { 68 | return "" 69 | } 70 | handler(w, req) 71 | return w.Body.String() 72 | } 73 | 74 | // HTTPBodyContains asserts that a specified handler returns a 75 | // body that contains a string. 76 | // 77 | // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 78 | // 79 | // Returns whether the assertion was successful (true) or not (false). 80 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) bool { 81 | body := HttpBody(handler, mode, url, values) 82 | 83 | contains := strings.Contains(body, fmt.Sprint(str)) 84 | if !contains { 85 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 86 | } 87 | 88 | return contains 89 | } 90 | 91 | // HTTPBodyNotContains asserts that a specified handler returns a 92 | // body that does not contain a string. 93 | // 94 | // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 95 | // 96 | // Returns whether the assertion was successful (true) or not (false). 97 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) bool { 98 | body := HttpBody(handler, mode, url, values) 99 | 100 | contains := strings.Contains(body, fmt.Sprint(str)) 101 | if contains { 102 | Fail(t, "Expected response body for %s to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body) 103 | } 104 | 105 | return !contains 106 | } 107 | 108 | // 109 | // Assertions Wrappers 110 | // 111 | 112 | // HTTPSuccess asserts that a specified handler returns a success status code. 113 | // 114 | // assert.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) 115 | // 116 | // Returns whether the assertion was successful (true) or not (false). 117 | func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, mode, url string, values url.Values) bool { 118 | return HTTPSuccess(a.t, handler, mode, url, values) 119 | } 120 | 121 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 122 | // 123 | // assert.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 124 | // 125 | // Returns whether the assertion was successful (true) or not (false). 126 | func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, mode, url string, values url.Values) bool { 127 | return HTTPRedirect(a.t, handler, mode, url, values) 128 | } 129 | 130 | // HTTPError asserts that a specified handler returns an error status code. 131 | // 132 | // assert.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 133 | // 134 | // Returns whether the assertion was successful (true) or not (false). 135 | func (a *Assertions) HTTPError(handler http.HandlerFunc, mode, url string, values url.Values) bool { 136 | return HTTPError(a.t, handler, mode, url, values) 137 | } 138 | 139 | // HTTPBodyContains asserts that a specified handler returns a 140 | // body that contains a string. 141 | // 142 | // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 143 | // 144 | // Returns whether the assertion was successful (true) or not (false). 145 | func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) bool { 146 | return HTTPBodyContains(a.t, handler, mode, url, values, str) 147 | } 148 | 149 | // HTTPBodyNotContains asserts that a specified handler returns a 150 | // body that does not contain a string. 151 | // 152 | // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 153 | // 154 | // Returns whether the assertion was successful (true) or not (false). 155 | func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, mode, url string, values url.Values, str interface{}) bool { 156 | return HTTPBodyNotContains(a.t, handler, mode, url, values, str) 157 | } 158 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/testify/assert/http_assertions_test.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | "testing" 8 | ) 9 | 10 | func httpOK(w http.ResponseWriter, r *http.Request) { 11 | w.WriteHeader(http.StatusOK) 12 | } 13 | 14 | func httpRedirect(w http.ResponseWriter, r *http.Request) { 15 | w.WriteHeader(http.StatusTemporaryRedirect) 16 | } 17 | 18 | func httpError(w http.ResponseWriter, r *http.Request) { 19 | w.WriteHeader(http.StatusInternalServerError) 20 | } 21 | 22 | func TestHTTPStatuses(t *testing.T) { 23 | assert := New(t) 24 | mockT := new(testing.T) 25 | 26 | assert.Equal(HTTPSuccess(mockT, httpOK, "GET", "/", nil), true) 27 | assert.Equal(HTTPSuccess(mockT, httpRedirect, "GET", "/", nil), false) 28 | assert.Equal(HTTPSuccess(mockT, httpError, "GET", "/", nil), false) 29 | 30 | assert.Equal(HTTPRedirect(mockT, httpOK, "GET", "/", nil), false) 31 | assert.Equal(HTTPRedirect(mockT, httpRedirect, "GET", "/", nil), true) 32 | assert.Equal(HTTPRedirect(mockT, httpError, "GET", "/", nil), false) 33 | 34 | assert.Equal(HTTPError(mockT, httpOK, "GET", "/", nil), false) 35 | assert.Equal(HTTPError(mockT, httpRedirect, "GET", "/", nil), false) 36 | assert.Equal(HTTPError(mockT, httpError, "GET", "/", nil), true) 37 | } 38 | 39 | func TestHTTPStatusesWrapper(t *testing.T) { 40 | assert := New(t) 41 | mockAssert := New(new(testing.T)) 42 | 43 | assert.Equal(mockAssert.HTTPSuccess(httpOK, "GET", "/", nil), true) 44 | assert.Equal(mockAssert.HTTPSuccess(httpRedirect, "GET", "/", nil), false) 45 | assert.Equal(mockAssert.HTTPSuccess(httpError, "GET", "/", nil), false) 46 | 47 | assert.Equal(mockAssert.HTTPRedirect(httpOK, "GET", "/", nil), false) 48 | assert.Equal(mockAssert.HTTPRedirect(httpRedirect, "GET", "/", nil), true) 49 | assert.Equal(mockAssert.HTTPRedirect(httpError, "GET", "/", nil), false) 50 | 51 | assert.Equal(mockAssert.HTTPError(httpOK, "GET", "/", nil), false) 52 | assert.Equal(mockAssert.HTTPError(httpRedirect, "GET", "/", nil), false) 53 | assert.Equal(mockAssert.HTTPError(httpError, "GET", "/", nil), true) 54 | } 55 | 56 | func httpHelloName(w http.ResponseWriter, r *http.Request) { 57 | name := r.FormValue("name") 58 | w.Write([]byte(fmt.Sprintf("Hello, %s!", name))) 59 | } 60 | 61 | func TestHttpBody(t *testing.T) { 62 | assert := New(t) 63 | mockT := new(testing.T) 64 | 65 | assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) 66 | assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) 67 | assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) 68 | 69 | assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) 70 | assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) 71 | assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) 72 | } 73 | 74 | func TestHttpBodyWrappers(t *testing.T) { 75 | assert := New(t) 76 | mockAssert := New(new(testing.T)) 77 | 78 | assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) 79 | assert.True(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) 80 | assert.False(mockAssert.HTTPBodyContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) 81 | 82 | assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) 83 | assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) 84 | assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/stretchr/testify/mock/doc.go: -------------------------------------------------------------------------------- 1 | // Provides a system by which it is possible to mock your objects and verify calls are happening as expected. 2 | // 3 | // Example Usage 4 | // 5 | // The mock package provides an object, Mock, that tracks activity on another object. It is usually 6 | // embedded into a test object as shown below: 7 | // 8 | // type MyTestObject struct { 9 | // // add a Mock object instance 10 | // mock.Mock 11 | // 12 | // // other fields go here as normal 13 | // } 14 | // 15 | // When implementing the methods of an interface, you wire your functions up 16 | // to call the Mock.Called(args...) method, and return the appropriate values. 17 | // 18 | // For example, to mock a method that saves the name and age of a person and returns 19 | // the year of their birth or an error, you might write this: 20 | // 21 | // func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { 22 | // args := o.Called(firstname, lastname, age) 23 | // return args.Int(0), args.Error(1) 24 | // } 25 | // 26 | // The Int, Error and Bool methods are examples of strongly typed getters that take the argument 27 | // index position. Given this argument list: 28 | // 29 | // (12, true, "Something") 30 | // 31 | // You could read them out strongly typed like this: 32 | // 33 | // args.Int(0) 34 | // args.Bool(1) 35 | // args.String(2) 36 | // 37 | // For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: 38 | // 39 | // return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) 40 | // 41 | // This may cause a panic if the object you are getting is nil (the type assertion will fail), in those 42 | // cases you should check for nil first. 43 | package mock 44 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/golang.org/x/net/netutil/listen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package netutil provides network utility functions, complementing the more 6 | // common ones in the net package. 7 | package netutil 8 | 9 | import ( 10 | "net" 11 | "sync" 12 | ) 13 | 14 | // LimitListener returns a Listener that accepts at most n simultaneous 15 | // connections from the provided Listener. 16 | func LimitListener(l net.Listener, n int) net.Listener { 17 | return &limitListener{l, make(chan struct{}, n)} 18 | } 19 | 20 | type limitListener struct { 21 | net.Listener 22 | sem chan struct{} 23 | } 24 | 25 | func (l *limitListener) acquire() { l.sem <- struct{}{} } 26 | func (l *limitListener) release() { <-l.sem } 27 | 28 | func (l *limitListener) Accept() (net.Conn, error) { 29 | l.acquire() 30 | c, err := l.Listener.Accept() 31 | if err != nil { 32 | l.release() 33 | return nil, err 34 | } 35 | return &limitListenerConn{Conn: c, release: l.release}, nil 36 | } 37 | 38 | type limitListenerConn struct { 39 | net.Conn 40 | releaseOnce sync.Once 41 | release func() 42 | } 43 | 44 | func (l *limitListenerConn) Close() error { 45 | err := l.Conn.Close() 46 | l.releaseOnce.Do(l.release) 47 | return err 48 | } 49 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/golang.org/x/net/netutil/listen_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.3 6 | 7 | // (We only run this test on Go 1.3 because the HTTP client timeout behavior 8 | // was bad in previous releases, causing occasional deadlocks.) 9 | 10 | package netutil 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "io" 16 | "io/ioutil" 17 | "net" 18 | "net/http" 19 | "sync" 20 | "sync/atomic" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestLimitListener(t *testing.T) { 26 | const ( 27 | max = 5 28 | num = 200 29 | ) 30 | 31 | l, err := net.Listen("tcp", "127.0.0.1:0") 32 | if err != nil { 33 | t.Fatalf("Listen: %v", err) 34 | } 35 | defer l.Close() 36 | l = LimitListener(l, max) 37 | 38 | var open int32 39 | go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | if n := atomic.AddInt32(&open, 1); n > max { 41 | t.Errorf("%d open connections, want <= %d", n, max) 42 | } 43 | defer atomic.AddInt32(&open, -1) 44 | time.Sleep(10 * time.Millisecond) 45 | fmt.Fprint(w, "some body") 46 | })) 47 | 48 | var wg sync.WaitGroup 49 | var failed int32 50 | for i := 0; i < num; i++ { 51 | wg.Add(1) 52 | go func() { 53 | defer wg.Done() 54 | c := http.Client{Timeout: 3 * time.Second} 55 | r, err := c.Get("http://" + l.Addr().String()) 56 | if err != nil { 57 | t.Logf("Get: %v", err) 58 | atomic.AddInt32(&failed, 1) 59 | return 60 | } 61 | defer r.Body.Close() 62 | io.Copy(ioutil.Discard, r.Body) 63 | }() 64 | } 65 | wg.Wait() 66 | 67 | // We expect some Gets to fail as the kernel's accept queue is filled, 68 | // but most should succeed. 69 | if failed >= num/2 { 70 | t.Errorf("too many Gets failed: %v", failed) 71 | } 72 | } 73 | 74 | type errorListener struct { 75 | net.Listener 76 | } 77 | 78 | func (errorListener) Accept() (net.Conn, error) { 79 | return nil, errFake 80 | } 81 | 82 | var errFake = errors.New("fake error from errorListener") 83 | 84 | // This used to hang. 85 | func TestLimitListenerError(t *testing.T) { 86 | donec := make(chan bool, 1) 87 | go func() { 88 | const n = 2 89 | ll := LimitListener(errorListener{}, n) 90 | for i := 0; i < n+1; i++ { 91 | _, err := ll.Accept() 92 | if err != errFake { 93 | t.Fatalf("Accept error = %v; want errFake", err) 94 | } 95 | } 96 | donec <- true 97 | }() 98 | select { 99 | case <-donec: 100 | case <-time.After(5 * time.Second): 101 | t.Fatal("timeout. deadlock?") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tyler Bunnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/README.md: -------------------------------------------------------------------------------- 1 | graceful [![GoDoc](https://godoc.org/github.com/tylerb/graceful?status.png)](http://godoc.org/github.com/tylerb/graceful) [![wercker status](https://app.wercker.com/status/2729ba763abf87695a17547e0f7af4a4/s "wercker status")](https://app.wercker.com/project/bykey/2729ba763abf87695a17547e0f7af4a4) 2 | ======== 3 | 4 | Graceful is a Go 1.3+ package enabling graceful shutdown of http.Handler servers. 5 | 6 | ## Installation 7 | 8 | To install, simply execute: 9 | 10 | ``` 11 | go get gopkg.in/tylerb/graceful.v1 12 | ``` 13 | 14 | I am using [gopkg.in](http://http://labix.org/gopkg.in) to control releases. 15 | 16 | ## Usage 17 | 18 | Using Graceful is esay. Simply create your http.Handler and pass it to the `Run` function: 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "github.com/tylerb/graceful" 25 | "net/http" 26 | "fmt" 27 | "time" 28 | ) 29 | 30 | func main() { 31 | mux := http.NewServeMux() 32 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 33 | fmt.Fprintf(w, "Welcome to the home page!") 34 | }) 35 | 36 | graceful.Run(":3001",10*time.Second,mux) 37 | } 38 | ``` 39 | 40 | Another example, using [Negroni](https://github.com/codegangsta/negroni), functions in much the same manner: 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "github.com/codegangsta/negroni" 47 | "github.com/tylerb/graceful" 48 | "net/http" 49 | "fmt" 50 | "time" 51 | ) 52 | 53 | func main() { 54 | mux := http.NewServeMux() 55 | mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 56 | fmt.Fprintf(w, "Welcome to the home page!") 57 | }) 58 | 59 | n := negroni.Classic() 60 | n.UseHandler(mux) 61 | //n.Run(":3000") 62 | graceful.Run(":3001",10*time.Second,n) 63 | } 64 | ``` 65 | 66 | In addition to Run there are the http.Server counterparts ListenAndServe, ListenAndServeTLS and Serve, which allow you to configure HTTPS, custom timeouts and error handling. 67 | Graceful may also be used by instantiating its Server type directly, which embeds an http.Server: 68 | 69 | ```go 70 | mux := // ... 71 | 72 | srv := &graceful.Server{ 73 | Timeout: 10 * time.Second, 74 | 75 | Server: &http.Server{ 76 | Addr: ":1234", 77 | Handler: mux, 78 | }, 79 | } 80 | 81 | srv.ListenAndServe() 82 | ``` 83 | 84 | This form allows you to set the ConnState callback, which works in the same way as in http.Server: 85 | 86 | ```go 87 | mux := // ... 88 | 89 | srv := &graceful.Server{ 90 | Timeout: 10 * time.Second, 91 | 92 | ConnState: func(conn net.Conn, state http.ConnState) { 93 | // conn has a new state 94 | }, 95 | 96 | Server: &http.Server{ 97 | Addr: ":1234", 98 | Handler: mux, 99 | }, 100 | } 101 | 102 | srv.ListenAndServe() 103 | ``` 104 | 105 | ## Behaviour 106 | 107 | When Graceful is sent a SIGINT or SIGTERM (possibly from ^C or a kill command), it: 108 | 109 | 1. Disables keepalive connections. 110 | 2. Closes the listening socket, allowing another process to listen on that port immediately. 111 | 3. Starts a timer of `timeout` duration to give active requests a chance to finish. 112 | 4. When timeout expires, closes all active connections. 113 | 5. Closes the `stopChan`, waking up any blocking goroutines. 114 | 6. Returns from the function, allowing the server to terminate. 115 | 116 | ## Notes 117 | 118 | If the `timeout` argument to `Run` is 0, the server never times out, allowing all active requests to complete. 119 | 120 | If you wish to stop the server in some way other than an OS signal, you may call the `Stop()` function. 121 | This function stops the server, gracefully, using the new timeout value you provide. The `StopChan()` function 122 | returns a channel on which you can block while waiting for the server to stop. This channel will be closed when 123 | the server is stopped, allowing your execution to proceed. Multiple goroutines can block on this channel at the 124 | same time and all will be signalled when stopping is complete. 125 | 126 | ## Contributing 127 | 128 | If you would like to contribute, please: 129 | 130 | 1. Create a GitHub issue regarding the contribution. Features and bugs should be discussed beforehand. 131 | 2. Fork the repository. 132 | 3. Create a pull request with your solution. This pull request should reference and close the issues (Fix #2). 133 | 134 | All pull requests should: 135 | 136 | 1. Pass [gometalinter -t .](https://github.com/alecthomas/gometalinter) with no warnings. 137 | 2. Be `go fmt` formatted. 138 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/graceful_test.go: -------------------------------------------------------------------------------- 1 | package graceful 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "reflect" 10 | "sync" 11 | "syscall" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | var killTime = 50 * time.Millisecond 17 | 18 | func runQuery(t *testing.T, expected int, shouldErr bool, wg *sync.WaitGroup) { 19 | wg.Add(1) 20 | defer wg.Done() 21 | client := http.Client{} 22 | r, err := client.Get("http://localhost:3000") 23 | if shouldErr && err == nil { 24 | t.Fatal("Expected an error but none was encountered.") 25 | } else if shouldErr && err != nil { 26 | if checkErr(t, err) { 27 | return 28 | } 29 | } 30 | if r != nil && r.StatusCode != expected { 31 | t.Fatalf("Incorrect status code on response. Expected %d. Got %d", expected, r.StatusCode) 32 | } else if r == nil { 33 | t.Fatal("No response when a response was expected.") 34 | } 35 | } 36 | 37 | func checkErr(t *testing.T, err error) bool { 38 | if err.(*url.Error).Err == io.EOF { 39 | return true 40 | } 41 | errno := err.(*url.Error).Err.(*net.OpError).Err.(syscall.Errno) 42 | if errno == syscall.ECONNREFUSED { 43 | return true 44 | } else if err != nil { 45 | t.Fatal("Error on Get:", err) 46 | } 47 | return false 48 | } 49 | 50 | func createListener(sleep time.Duration) (*http.Server, net.Listener, error) { 51 | mux := http.NewServeMux() 52 | mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { 53 | time.Sleep(sleep) 54 | rw.WriteHeader(http.StatusOK) 55 | }) 56 | 57 | server := &http.Server{Addr: ":3000", Handler: mux} 58 | l, err := net.Listen("tcp", ":3000") 59 | return server, l, err 60 | } 61 | 62 | func runServer(timeout, sleep time.Duration, c chan os.Signal) error { 63 | server, l, err := createListener(sleep) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | srv := &Server{Timeout: timeout, Server: server, interrupt: c} 69 | return srv.Serve(l) 70 | } 71 | 72 | func launchTestQueries(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) { 73 | for i := 0; i < 8; i++ { 74 | go runQuery(t, http.StatusOK, false, wg) 75 | } 76 | 77 | time.Sleep(10 * time.Millisecond) 78 | c <- os.Interrupt 79 | time.Sleep(10 * time.Millisecond) 80 | 81 | for i := 0; i < 8; i++ { 82 | go runQuery(t, 0, true, wg) 83 | } 84 | 85 | wg.Done() 86 | } 87 | 88 | func TestGracefulRun(t *testing.T) { 89 | c := make(chan os.Signal, 1) 90 | 91 | var wg sync.WaitGroup 92 | wg.Add(1) 93 | 94 | go func() { 95 | runServer(killTime, killTime/2, c) 96 | wg.Done() 97 | }() 98 | 99 | wg.Add(1) 100 | go launchTestQueries(t, &wg, c) 101 | wg.Wait() 102 | } 103 | 104 | func TestGracefulRunTimesOut(t *testing.T) { 105 | c := make(chan os.Signal, 1) 106 | 107 | var wg sync.WaitGroup 108 | wg.Add(1) 109 | 110 | go func() { 111 | runServer(killTime, killTime*10, c) 112 | wg.Done() 113 | }() 114 | 115 | wg.Add(1) 116 | go func() { 117 | for i := 0; i < 8; i++ { 118 | go runQuery(t, 0, true, &wg) 119 | } 120 | time.Sleep(10 * time.Millisecond) 121 | c <- os.Interrupt 122 | time.Sleep(10 * time.Millisecond) 123 | for i := 0; i < 8; i++ { 124 | go runQuery(t, 0, true, &wg) 125 | } 126 | wg.Done() 127 | }() 128 | 129 | wg.Wait() 130 | 131 | } 132 | 133 | func TestGracefulRunDoesntTimeOut(t *testing.T) { 134 | c := make(chan os.Signal, 1) 135 | 136 | var wg sync.WaitGroup 137 | wg.Add(1) 138 | 139 | go func() { 140 | runServer(0, killTime*2, c) 141 | wg.Done() 142 | }() 143 | 144 | wg.Add(1) 145 | go launchTestQueries(t, &wg, c) 146 | wg.Wait() 147 | } 148 | 149 | func TestGracefulRunNoRequests(t *testing.T) { 150 | c := make(chan os.Signal, 1) 151 | 152 | var wg sync.WaitGroup 153 | wg.Add(1) 154 | 155 | go func() { 156 | runServer(0, killTime*2, c) 157 | wg.Done() 158 | }() 159 | 160 | c <- os.Interrupt 161 | 162 | wg.Wait() 163 | 164 | } 165 | 166 | func TestGracefulForwardsConnState(t *testing.T) { 167 | c := make(chan os.Signal, 1) 168 | states := make(map[http.ConnState]int) 169 | var stateLock sync.Mutex 170 | 171 | connState := func(conn net.Conn, state http.ConnState) { 172 | stateLock.Lock() 173 | states[state]++ 174 | stateLock.Unlock() 175 | } 176 | 177 | var wg sync.WaitGroup 178 | wg.Add(1) 179 | 180 | expected := map[http.ConnState]int{ 181 | http.StateNew: 8, 182 | http.StateActive: 8, 183 | http.StateClosed: 8, 184 | } 185 | 186 | go func() { 187 | server, l, _ := createListener(killTime / 2) 188 | srv := &Server{ 189 | ConnState: connState, 190 | Timeout: killTime, 191 | Server: server, 192 | interrupt: c, 193 | } 194 | srv.Serve(l) 195 | 196 | wg.Done() 197 | }() 198 | 199 | wg.Add(1) 200 | go launchTestQueries(t, &wg, c) 201 | wg.Wait() 202 | 203 | stateLock.Lock() 204 | if !reflect.DeepEqual(states, expected) { 205 | t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected) 206 | } 207 | stateLock.Unlock() 208 | } 209 | 210 | func TestGracefulExplicitStop(t *testing.T) { 211 | server, l, err := createListener(1 * time.Millisecond) 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | 216 | srv := &Server{Timeout: killTime, Server: server} 217 | 218 | go func() { 219 | go srv.Serve(l) 220 | time.Sleep(10 * time.Millisecond) 221 | srv.Stop(killTime) 222 | }() 223 | 224 | // block on the stopChan until the server has shut down 225 | select { 226 | case <-srv.StopChan(): 227 | case <-time.After(100 * time.Millisecond): 228 | t.Fatal("Timed out while waiting for explicit stop to complete") 229 | } 230 | } 231 | 232 | func TestGracefulExplicitStopOverride(t *testing.T) { 233 | server, l, err := createListener(1 * time.Millisecond) 234 | if err != nil { 235 | t.Fatal(err) 236 | } 237 | 238 | srv := &Server{Timeout: killTime, Server: server} 239 | 240 | go func() { 241 | go srv.Serve(l) 242 | time.Sleep(10 * time.Millisecond) 243 | srv.Stop(killTime / 2) 244 | }() 245 | 246 | // block on the stopChan until the server has shut down 247 | select { 248 | case <-srv.StopChan(): 249 | case <-time.After(killTime): 250 | t.Fatal("Timed out while waiting for explicit stop to complete") 251 | } 252 | } 253 | 254 | func TestShutdownInitiatedCallback(t *testing.T) { 255 | server, l, err := createListener(1 * time.Millisecond) 256 | if err != nil { 257 | t.Fatal(err) 258 | } 259 | 260 | called := make(chan struct{}) 261 | cb := func() { close(called) } 262 | 263 | srv := &Server{Server: server, ShutdownInitiated: cb} 264 | 265 | go func() { 266 | go srv.Serve(l) 267 | time.Sleep(10 * time.Millisecond) 268 | srv.Stop(killTime) 269 | }() 270 | 271 | select { 272 | case <-called: 273 | case <-time.After(killTime): 274 | t.Fatal("Timed out while waiting for ShutdownInitiated callback to be called") 275 | } 276 | } 277 | func hijackingListener(srv *Server) (*http.Server, net.Listener, error) { 278 | mux := http.NewServeMux() 279 | mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) { 280 | conn, bufrw, err := rw.(http.Hijacker).Hijack() 281 | if err != nil { 282 | http.Error(rw, "webserver doesn't support hijacking", http.StatusInternalServerError) 283 | return 284 | } 285 | 286 | defer conn.Close() 287 | 288 | bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n") 289 | bufrw.Flush() 290 | }) 291 | 292 | server := &http.Server{Addr: ":3000", Handler: mux} 293 | l, err := net.Listen("tcp", ":3000") 294 | return server, l, err 295 | } 296 | 297 | func TestNotifyClosed(t *testing.T) { 298 | c := make(chan os.Signal, 1) 299 | 300 | var wg sync.WaitGroup 301 | wg.Add(1) 302 | 303 | srv := &Server{Timeout: killTime, interrupt: c} 304 | server, l, err := hijackingListener(srv) 305 | if err != nil { 306 | t.Fatal(err) 307 | } 308 | 309 | srv.Server = server 310 | 311 | go func() { 312 | srv.Serve(l) 313 | wg.Done() 314 | }() 315 | 316 | for i := 0; i < 8; i++ { 317 | runQuery(t, http.StatusOK, false, &wg) 318 | } 319 | 320 | srv.Stop(0) 321 | 322 | // block on the stopChan until the server has shut down 323 | select { 324 | case <-srv.StopChan(): 325 | case <-time.After(100 * time.Millisecond): 326 | t.Fatal("Timed out while waiting for explicit stop to complete") 327 | } 328 | 329 | if len(srv.connections) > 0 { 330 | t.Fatal("hijacked connections should not be managed") 331 | } 332 | 333 | } 334 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/codegangsta/negroni" 8 | "github.com/tylerb/graceful" 9 | ) 10 | 11 | func main() { 12 | 13 | var wg sync.WaitGroup 14 | 15 | wg.Add(3) 16 | go func() { 17 | n := negroni.New() 18 | fmt.Println("Launching server on :3000") 19 | graceful.Run(":3000", 0, n) 20 | fmt.Println("Terminated server on :3000") 21 | wg.Done() 22 | }() 23 | go func() { 24 | n := negroni.New() 25 | fmt.Println("Launching server on :3001") 26 | graceful.Run(":3001", 0, n) 27 | fmt.Println("Terminated server on :3001") 28 | wg.Done() 29 | }() 30 | go func() { 31 | n := negroni.New() 32 | fmt.Println("Launching server on :3002") 33 | graceful.Run(":3002", 0, n) 34 | fmt.Println("Terminated server on :3002") 35 | wg.Done() 36 | }() 37 | fmt.Println("Press ctrl+c. All servers should terminate.") 38 | wg.Wait() 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/gopkg.in/tylerb/graceful.v1/wercker.yml: -------------------------------------------------------------------------------- 1 | box: wercker/golang 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageLayers API 2 | 3 | [![](https://badge.imagelayers.io/microscaling/imagelayers-api.svg)](https://imagelayers.io/?images=microscaling/imagelayers-api:latest 'Get your own badge on imagelayers.io') [![](https://images.microbadger.com/badges/version/microscaling/imagelayers-api.svg)](http://microbadger.com/images/microscaling/imagelayers-api "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/commit/microscaling/imagelayers-api.svg)](http://microbadger.com/images/microscaling/imagelayers-api "Get your own commit badge on microbadger.com") 4 | 5 | [ImageLayers.io](https://imagelayers.io) is a project maintained by [Microscaling Systems](https://microscaling.com) since September 2016. The project was created and developed by the team at [CenturyLink Labs](http://www.centurylinklabs.com/). This utility provides a browser-based visualization of user-specified Docker Images and their layers. This visualization provides key information on the composition of a Docker Image and any [commonalities between them](https://imagelayers.io/?images=java:latest,golang:latest,node:latest,python:latest,php:latest,ruby:latest). ImageLayers.io allows Docker users to easily discover best practices for image construction, and aid in determining which images are most appropriate for their specific use cases. Similar to ```docker images --tree```, the ImageLayers project aims to make visualizing your image cache easier, so that you may identify images that take up excessive space and create smarter base images for your Docker projects. 6 | 7 | ## Installation 8 | 9 | The ImageLayers API is a golang application and uses [godep](https://github.com/tools/godep). 10 | 11 | ``` 12 | $ go get github.com/microscaling/imagelayers 13 | $ go get github.com/tools/godep 14 | $ cd $GOPATH/src/github.com/microscaling/imagelayers 15 | $ godep restore 16 | $ go test ./... #should all pass 17 | $ go run main.go #or build and run 18 | ``` 19 | 20 | ## Build Docker image 21 | 22 | ``` 23 | # Compile the Go binary for Linux with static linking for Alpine 24 | $ GOOS=linux CGO_ENABLED=0 go build -a -installsuffix cgo -o imagelayers 25 | 26 | # Build the Docker image using args for the labels 27 | $ docker build --tag microscaling/imagelayers-api:$(cat VERSION) \ 28 | --build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \ 29 | --build-arg VCS_REF=`git rev-parse --short HEAD` \ 30 | --build-arg VERSION=`cat VERSION` . 31 | ``` 32 | 33 | ## imagelayers-graph UI Project 34 | ImageLayers provides services to search and analyze mulitple images within the docker registry. [Imagelayers Graph](https://github.com/microscaling/imagelayers-graph/) is an example interface using these services. 35 | 36 | ## ImageLayers In Action 37 | You can see the hosted version of ImageLayers at [imagelayers.io](https://imagelayers.io). 38 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.2 2 | -------------------------------------------------------------------------------- /api/registry.go: -------------------------------------------------------------------------------- 1 | package api // import "github.com/microscaling/imagelayers/api" 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "sync" 10 | "time" 11 | 12 | "github.com/CenturyLinkLabs/docker-reg-client/registry" 13 | "github.com/gorilla/mux" 14 | "github.com/microscaling/imagelayers/server" 15 | "github.com/pmylund/go-cache" 16 | ) 17 | 18 | const ( 19 | cacheDuration = 15 * time.Minute 20 | cacheCleanupInterval = 5 * time.Minute 21 | ) 22 | 23 | type Status struct { 24 | Message string `json:"message"` 25 | Service string `json:"service"` 26 | } 27 | 28 | type Request struct { 29 | Repos []Repo `json:"repos"` 30 | } 31 | 32 | type Response struct { 33 | Repo *Repo `json:"repo"` 34 | Layers []*registry.ImageMetadata `json:"layers"` 35 | Status int `json:"status"` 36 | } 37 | 38 | type Repo struct { 39 | Name string `json:"name"` 40 | Tag string `json:"tag"` 41 | Size int64 `json:"size"` 42 | Count int `json:"count"` 43 | } 44 | 45 | type RegistryConnection interface { 46 | Status() (Status, error) 47 | GetTags(string) (registry.TagMap, error) 48 | Search(string) (*registry.SearchResults, error) 49 | GetImageLayers(name, tag string) ([]*registry.ImageMetadata, error) 50 | } 51 | 52 | type registryApi struct { 53 | connection RegistryConnection 54 | imageCache *cache.Cache 55 | } 56 | 57 | func newRegistryApi(conn RegistryConnection) *registryApi { 58 | return ®istryApi{ 59 | connection: conn, 60 | imageCache: cache.New(cacheDuration, cacheCleanupInterval), 61 | } 62 | } 63 | 64 | func (reg *registryApi) Routes(context string, router *server.Router) { 65 | routes := server.RouteMap{ 66 | "GET": { 67 | "/status": reg.handleStatus, 68 | "/search": reg.handleSearch, 69 | "/images/{front}/tags": reg.handleTags, 70 | "/images/{front}/{tail}/tags": reg.handleTags, 71 | }, 72 | "POST": { 73 | "/analyze": reg.handleAnalysis, 74 | }, 75 | } 76 | 77 | router.AddCorsRoutes(context, routes) 78 | } 79 | 80 | func (reg *registryApi) handleTags(w http.ResponseWriter, r *http.Request) { 81 | image := mux.Vars(r)["front"] 82 | tail := mux.Vars(r)["tail"] 83 | 84 | if tail != "" { 85 | image = image + "/" + tail 86 | } 87 | 88 | res, err := reg.connection.GetTags(image) 89 | if err != nil { 90 | respondToError(w, err) 91 | return 92 | } 93 | 94 | respondWithJSON(w, res) 95 | } 96 | 97 | func (reg *registryApi) handleSearch(w http.ResponseWriter, r *http.Request) { 98 | value := r.FormValue("name") 99 | 100 | res, err := reg.connection.Search(value) 101 | if err != nil { 102 | respondToError(w, err) 103 | return 104 | } 105 | 106 | respondWithJSON(w, res) 107 | } 108 | 109 | func (reg *registryApi) handleStatus(w http.ResponseWriter, r *http.Request) { 110 | res, err := reg.connection.Status() 111 | if err != nil { 112 | respondToError(w, err) 113 | return 114 | } 115 | log.Printf("Status: %s", res.Service) 116 | 117 | respondWithJSON(w, res) 118 | } 119 | 120 | func (reg *registryApi) handleAnalysis(w http.ResponseWriter, r *http.Request) { 121 | var request Request 122 | 123 | body, err := ioutil.ReadAll(r.Body) 124 | if err != nil { 125 | respondToError(w, err) 126 | return 127 | } 128 | 129 | if err := json.Unmarshal(body, &request); err != nil { 130 | respondToError(w, err) 131 | return 132 | } 133 | 134 | res := reg.inspectImages(request.Repos) 135 | 136 | respondWithJSON(w, res) 137 | } 138 | 139 | func (reg *registryApi) inspectImages(images []Repo) []*Response { 140 | var wg sync.WaitGroup 141 | list := make([]*Response, len(images)) 142 | 143 | for i, image := range images { 144 | wg.Add(1) 145 | go func(idx int, img Repo) { 146 | defer wg.Done() 147 | var resp *Response 148 | 149 | key := fmt.Sprintf("%s:%s", img.Name, img.Tag) 150 | val, found := reg.imageCache.Get(key) 151 | if found { 152 | resp = val.(*Response) 153 | } else { 154 | resp = reg.loadMetaData(img) 155 | if resp.Status == http.StatusOK { 156 | reg.imageCache.Set(key, resp, cache.DefaultExpiration) 157 | } 158 | } 159 | 160 | list[idx] = resp 161 | }(i, image) 162 | } 163 | 164 | wg.Wait() 165 | return list 166 | } 167 | 168 | func (reg *registryApi) loadMetaData(repo Repo) *Response { 169 | resp := new(Response) 170 | resp.Repo = &repo 171 | 172 | layers, err := reg.connection.GetImageLayers(repo.Name, repo.Tag) 173 | if err == nil { 174 | resp.Status = http.StatusOK 175 | resp.Layers = layers 176 | resp.Repo.Count = len(resp.Layers) 177 | 178 | for _, layer := range resp.Layers { 179 | resp.Repo.Size += layer.Size 180 | } 181 | } else { 182 | switch e := err.(type) { 183 | case registry.RegistryError: 184 | resp.Status = e.Code 185 | default: 186 | resp.Status = http.StatusInternalServerError 187 | } 188 | log.Printf("Error: %s", err) 189 | } 190 | 191 | return resp 192 | } 193 | 194 | func respondToError(w http.ResponseWriter, err error) { 195 | w.WriteHeader(http.StatusInternalServerError) 196 | fmt.Fprint(w, err.Error()) 197 | } 198 | 199 | func respondWithJSON(w http.ResponseWriter, o interface{}) { 200 | if err := json.NewEncoder(w).Encode(o); err != nil { 201 | respondToError(w, err) 202 | return 203 | } 204 | w.Header().Set("Content-Type", "application/json") 205 | } 206 | -------------------------------------------------------------------------------- /api/registry_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "net/http/httptest" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/CenturyLinkLabs/docker-reg-client/registry" 12 | "github.com/gorilla/mux" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/mock" 15 | ) 16 | 17 | type mockConnection struct { 18 | mock.Mock 19 | } 20 | 21 | func (m *mockConnection) GetTags(name string) (registry.TagMap, error) { 22 | args := m.Mock.Called(name) 23 | return args.Get(0).(registry.TagMap), args.Error(1) 24 | } 25 | 26 | func (m *mockConnection) Search(name string) (*registry.SearchResults, error) { 27 | args := m.Mock.Called(name) 28 | var r *registry.SearchResults 29 | if a := args.Get(0); a != nil { 30 | r = a.(*registry.SearchResults) 31 | } 32 | return r, args.Error(1) 33 | } 34 | 35 | func (m *mockConnection) Status() (Status, error) { 36 | args := m.Mock.Called() 37 | return args.Get(0).(Status), args.Error(1) 38 | } 39 | 40 | func (m *mockConnection) GetImageLayers(name, tag string) ([]*registry.ImageMetadata, error) { 41 | args := m.Mock.Called(name, tag) 42 | return args.Get(0).([]*registry.ImageMetadata), args.Error(1) 43 | } 44 | 45 | func TestMarshalStatus(t *testing.T) { 46 | status := Status{Message: "active", Service: "foo"} 47 | jsonTest, _ := json.Marshal(status) 48 | 49 | assert.Equal(t, `{"message":"active","service":"foo"}`, string(jsonTest)) 50 | } 51 | 52 | func TestMarshalRequest(t *testing.T) { 53 | repo1 := Repo{Name: "foo", Tag: "latest"} 54 | repo2 := Repo{Name: "bar", Tag: "latest"} 55 | imageList := []Repo{repo1, repo2} 56 | repos := Request{Repos: imageList} 57 | jsonTest, _ := json.Marshal(repos) 58 | 59 | assert.Equal(t, `{"repos":[{"name":"foo","tag":"latest","size":0,"count":0},{"name":"bar","tag":"latest","size":0,"count":0}]}`, string(jsonTest)) 60 | } 61 | 62 | func TestAnalyzeRequest(t *testing.T) { 63 | // setup 64 | fakeConn := new(mockConnection) 65 | api := newRegistryApi(fakeConn) 66 | inBody := `{"repos":[{"name":"foo","tag":"latest"}]}` 67 | 68 | // build request 69 | req, _ := http.NewRequest("POST", "http://localhost/analyze", strings.NewReader(inBody)) 70 | w := httptest.NewRecorder() 71 | 72 | // build response 73 | metadata := new(registry.ImageMetadata) 74 | resp := make([]*registry.ImageMetadata, 1) 75 | resp[0] = metadata 76 | 77 | // test 78 | fakeConn.On("GetImageLayers", "foo", "latest").Return([]*registry.ImageMetadata{metadata}, nil) 79 | api.handleAnalysis(w, req) 80 | 81 | // asserts 82 | fakeConn.AssertExpectations(t) 83 | assert.Equal(t, http.StatusOK, w.Code) 84 | } 85 | 86 | func TestAnalyzeRequestErroredBadParameters(t *testing.T) { 87 | // setup 88 | fakeConn := new(mockConnection) 89 | api := newRegistryApi(fakeConn) 90 | inBody := "HAIL AND WELL MET!!! I AM BAD JSON!!1!!" 91 | 92 | // build request 93 | req, _ := http.NewRequest("POST", "http://localhost/analyze", strings.NewReader(inBody)) 94 | w := httptest.NewRecorder() 95 | 96 | // test 97 | api.handleAnalysis(w, req) 98 | 99 | // asserts 100 | fakeConn.AssertExpectations(t) 101 | assert.Equal(t, http.StatusInternalServerError, w.Code) 102 | assert.Contains(t, w.Body.String(), "invalid character") 103 | } 104 | 105 | func TestAnalyzeRequestWithRegistryError(t *testing.T) { 106 | // setup 107 | fakeConn := new(mockConnection) 108 | api := newRegistryApi(fakeConn) 109 | inBody := `{"repos":[{"name":"foo","tag":"latest"}]}` 110 | 111 | // build request 112 | req, _ := http.NewRequest("POST", "http://localhost/analyze", strings.NewReader(inBody)) 113 | w := httptest.NewRecorder() 114 | 115 | // test 116 | err := errors.New("test error") 117 | fakeConn.On("GetImageLayers", "foo", "latest").Return([]*registry.ImageMetadata{}, err) 118 | api.handleAnalysis(w, req) 119 | 120 | // asserts 121 | fakeConn.AssertExpectations(t) 122 | assert.Equal(t, http.StatusOK, w.Code) 123 | if assert.NotNil(t, w.Body) { 124 | var r []*Response 125 | assert.NoError(t, json.Unmarshal([]byte(w.Body.String()), &r)) 126 | 127 | if assert.Len(t, r, 1) { 128 | assert.Equal(t, http.StatusInternalServerError, r[0].Status) 129 | } 130 | } 131 | } 132 | 133 | func TestStatusRequest(t *testing.T) { 134 | // setup 135 | fakeConn := new(mockConnection) 136 | api := newRegistryApi(fakeConn) 137 | 138 | // build request 139 | resp := Status{Message: "foo", Service: "bar"} 140 | req, _ := http.NewRequest("GET", "http://localhost/status", strings.NewReader("{}")) 141 | w := httptest.NewRecorder() 142 | 143 | // test 144 | fakeConn.On("Status").Return(resp, nil) 145 | api.handleStatus(w, req) 146 | 147 | // asserts 148 | fakeConn.AssertExpectations(t) 149 | assert.Equal(t, http.StatusOK, w.Code) 150 | assert.Equal(t, `{"message":"foo","service":"bar"}`, strings.TrimSpace(w.Body.String())) 151 | } 152 | 153 | func TestStatusRequestWithRegistryError(t *testing.T) { 154 | // setup 155 | fakeConn := new(mockConnection) 156 | api := newRegistryApi(fakeConn) 157 | 158 | // build request 159 | req, _ := http.NewRequest("GET", "http://localhost/status", strings.NewReader("{}")) 160 | w := httptest.NewRecorder() 161 | 162 | // test 163 | fakeConn.On("Status").Return(Status{}, errors.New("test error")) 164 | api.handleStatus(w, req) 165 | 166 | // asserts 167 | fakeConn.AssertExpectations(t) 168 | assert.Equal(t, http.StatusInternalServerError, w.Code) 169 | assert.Equal(t, "test error", w.Body.String()) 170 | } 171 | 172 | func TestSearchRequest(t *testing.T) { 173 | // setup 174 | fakeConn := new(mockConnection) 175 | api := newRegistryApi(fakeConn) 176 | 177 | // build request 178 | req, _ := http.NewRequest("GET", "http://localhost/search?name=foo", strings.NewReader("{}")) 179 | w := httptest.NewRecorder() 180 | 181 | // test 182 | results := registry.SearchResults{Query: "Test Query"} 183 | fakeConn.On("Search", "foo").Return(&results, nil) 184 | api.handleSearch(w, req) 185 | 186 | // asserts 187 | fakeConn.AssertExpectations(t) 188 | assert.Equal(t, http.StatusOK, w.Code) 189 | assert.Contains(t, w.Body.String(), "Test Query") 190 | } 191 | 192 | func TestSearchRequestWithRegistryError(t *testing.T) { 193 | // setup 194 | fakeConn := new(mockConnection) 195 | api := newRegistryApi(fakeConn) 196 | 197 | // build request 198 | req, _ := http.NewRequest("GET", "http://localhost/search?name=foo", strings.NewReader("{}")) 199 | w := httptest.NewRecorder() 200 | 201 | // test 202 | fakeConn.On("Search", "foo").Return(nil, errors.New("test error")) 203 | api.handleSearch(w, req) 204 | 205 | // asserts 206 | fakeConn.AssertExpectations(t) 207 | assert.Equal(t, http.StatusInternalServerError, w.Code) 208 | assert.Equal(t, "test error", w.Body.String()) 209 | } 210 | 211 | func TestGetTagsRequestWithSlash(t *testing.T) { 212 | // setup 213 | image := "centurylink/dray" 214 | fakeConn := new(mockConnection) 215 | api := newRegistryApi(fakeConn) 216 | var res registry.TagMap 217 | fakeConn.On("GetTags", image).Return(res, nil) 218 | 219 | // build request 220 | req, _ := http.NewRequest("GET", "http://localhost/images/centurylink%2Fdray/tags", strings.NewReader("{}")) 221 | w := httptest.NewRecorder() 222 | m := mux.NewRouter() 223 | m.HandleFunc("/images/{front}/{tail}/tags", api.handleTags).Methods("GET") 224 | 225 | // test 226 | m.ServeHTTP(w, req) 227 | 228 | // asserts 229 | fakeConn.AssertExpectations(t) 230 | assert.Equal(t, http.StatusOK, w.Code) 231 | } 232 | 233 | func TestGetTagsRequest(t *testing.T) { 234 | // setup 235 | image := "redis" 236 | fakeConn := new(mockConnection) 237 | api := newRegistryApi(fakeConn) 238 | res := registry.TagMap{"Key": "Value"} 239 | fakeConn.On("GetTags", image).Return(res, nil) 240 | 241 | // build request 242 | req, _ := http.NewRequest("GET", "http://localhost/images/redis/tags", strings.NewReader("{}")) 243 | w := httptest.NewRecorder() 244 | m := mux.NewRouter() 245 | m.HandleFunc("/images/{front}/tags", api.handleTags).Methods("GET") 246 | 247 | // test 248 | m.ServeHTTP(w, req) 249 | 250 | // asserts 251 | fakeConn.AssertExpectations(t) 252 | assert.Equal(t, http.StatusOK, w.Code) 253 | assert.Equal(t, `{"Key":"Value"}`, strings.TrimSpace(w.Body.String())) 254 | } 255 | 256 | func TestGetTagsRequestWithRegistryError(t *testing.T) { 257 | // setup 258 | image := "centurylink/dray" 259 | fakeConn := new(mockConnection) 260 | api := newRegistryApi(fakeConn) 261 | fakeConn.On("GetTags", image).Return(registry.TagMap{}, errors.New("test error")) 262 | 263 | // build request 264 | req, _ := http.NewRequest("GET", "http://localhost/images/centurylink%2Fdray/tags", strings.NewReader("{}")) 265 | w := httptest.NewRecorder() 266 | m := mux.NewRouter() 267 | m.HandleFunc("/images/{front}/{tail}/tags", api.handleTags).Methods("GET") 268 | 269 | // test 270 | m.ServeHTTP(w, req) 271 | 272 | // asserts 273 | fakeConn.AssertExpectations(t) 274 | assert.Equal(t, http.StatusInternalServerError, w.Code) 275 | assert.Equal(t, "test error", w.Body.String()) 276 | } 277 | -------------------------------------------------------------------------------- /api/remote.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/CenturyLinkLabs/docker-reg-client/registry" 8 | "github.com/pmylund/go-cache" 9 | ) 10 | 11 | type remoteConnection struct { 12 | client *registry.Client 13 | layerCache *cache.Cache 14 | tagCache *cache.Cache 15 | } 16 | 17 | func NewRemoteRegistry() *registryApi { 18 | conn := &remoteConnection{ 19 | client: registry.NewClient(), 20 | layerCache: cache.New(cacheDuration, cacheCleanupInterval), 21 | tagCache: cache.New(cacheDuration, cacheCleanupInterval), 22 | } 23 | reg := newRegistryApi(conn) 24 | 25 | return reg 26 | } 27 | 28 | func (rc *remoteConnection) GetTags(name string) (registry.TagMap, error) { 29 | var tags registry.TagMap 30 | 31 | val, found := rc.tagCache.Get(name) 32 | if found { 33 | tags = val.(registry.TagMap) 34 | } else { 35 | auth, err := rc.client.Hub.GetReadToken(name) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | tags, err = rc.client.Repository.ListTags(name, auth) 41 | if err != nil { 42 | return nil, err 43 | } 44 | rc.tagCache.Set(name, tags, cache.DefaultExpiration) 45 | } 46 | 47 | return tags, nil 48 | } 49 | 50 | func (rc *remoteConnection) Search(name string) (*registry.SearchResults, error) { 51 | return rc.client.Search.Query(name, 1, 10) 52 | } 53 | 54 | func (rc *remoteConnection) Status() (Status, error) { 55 | var s Status 56 | 57 | s.Message = "Connected to Registry" 58 | s.Service = "Registry Image Manager" 59 | return s, nil 60 | } 61 | 62 | func (rc *remoteConnection) GetImageLayers(name, tag string) ([]*registry.ImageMetadata, error) { 63 | var wg sync.WaitGroup 64 | 65 | auth, err := rc.client.Hub.GetReadToken(name) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | id, err := rc.client.Repository.GetImageID(name, tag, auth) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | layerIDs, err := rc.client.Image.GetAncestry(id, auth) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | list := make([]*registry.ImageMetadata, len(layerIDs)) 81 | 82 | for i, layerID := range layerIDs { 83 | wg.Add(1) 84 | go func(idx int, lid string) { 85 | defer wg.Done() 86 | var err error 87 | var m *registry.ImageMetadata 88 | 89 | val, found := rc.layerCache.Get(lid) 90 | if found { 91 | m = val.(*registry.ImageMetadata) 92 | } else { 93 | m, err = rc.client.Image.GetMetadata(lid, auth) 94 | if err == nil && m != nil { 95 | rc.layerCache.Set(lid, m, cache.DefaultExpiration) 96 | } 97 | } 98 | 99 | list[idx] = m 100 | }(i, layerID) 101 | } 102 | wg.Wait() 103 | 104 | // Return an error if any of the layers are missing 105 | for i, layer := range list { 106 | if layer == nil { 107 | return nil, fmt.Errorf("Error retrieving layer: %s", layerIDs[i]) 108 | } 109 | } 110 | 111 | return list, nil 112 | } 113 | -------------------------------------------------------------------------------- /api/remote_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRemoteConnectionInterface(t *testing.T) { 10 | assert.Implements(t, (*RegistryConnection)(nil), new(remoteConnection)) 11 | } 12 | 13 | func TestStatus(t *testing.T) { 14 | rc := new(remoteConnection) 15 | s, _ := rc.Status() 16 | 17 | assert.Equal(t, s.Message, "Connected to Registry") 18 | assert.Equal(t, s.Service, "Registry Image Manager") 19 | } 20 | -------------------------------------------------------------------------------- /bin/deploy_qa.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ssh root@8.22.8.236 "docker rm -f imagelayers_api; docker rmi -f centurylink/imagelayers-api:qa; docker pull centurylink/imagelayers-api:qa; docker run -d --name imagelayers_api -p 8888:8888 centurylink/imagelayers-api:qa" -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker run --rm \ 4 | -v $(pwd):/src \ 5 | -v /var/run/docker.sock:/var/run/docker.sock \ 6 | centurylink/golang-builder:latest \ 7 | centurylink/imagelayers:latest 8 | -------------------------------------------------------------------------------- /build_qa.sh: -------------------------------------------------------------------------------- 1 | docker run --rm \ 2 | -v $(pwd):/src \ 3 | -v /var/run/docker.sock:/var/run/docker.sock \ 4 | centurylink/golang-builder:latest \ 5 | centurylink/imagelayers-api:qa -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | services: 3 | - docker 4 | 5 | general: 6 | artifacts: 7 | - "imagelayers-api" # the built binary 8 | 9 | dependencies: 10 | override: 11 | # This is a no-op to get it to not try and build our Go app (and possibly 12 | # fail due to Godep stuff). 13 | - "true" 14 | 15 | test: 16 | override: 17 | - docker run -v $(pwd):/src centurylink/golang-tester 18 | 19 | deployment: 20 | hub: 21 | branch: master 22 | owner: CenturyLinkLabs 23 | commands: 24 | - docker run -v $(pwd):/src centurylink/golang-builder:latest 25 | - docker build -t centurylink/imagelayers-api:qa . 26 | - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS 27 | - docker push centurylink/imagelayers-api:qa 28 | Production: 29 | branch: release 30 | owner: CenturyLinkLabs 31 | commands: 32 | - docker run -v $(pwd):/src centurylink/golang-builder:latest 33 | - docker build -t centurylink/imagelayers-api:latest . 34 | - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS 35 | - docker push centurylink/imagelayers-api:latest 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main // import "github.com/microscaling/imagelayers" 2 | 3 | import ( 4 | "expvar" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/gorilla/mux" 12 | "github.com/microscaling/imagelayers/api" 13 | "github.com/microscaling/imagelayers/server" 14 | "gopkg.in/tylerb/graceful.v1" 15 | ) 16 | 17 | type layerServer struct { 18 | } 19 | 20 | func NewServer() *layerServer { 21 | return new(layerServer) 22 | } 23 | 24 | func (s *layerServer) Start(port int) { 25 | router := s.createRouter() 26 | 27 | log.Printf("Server starting on port %d", port) 28 | portString := fmt.Sprintf(":%d", port) 29 | graceful.Run(portString, 10*time.Second, router) 30 | } 31 | 32 | func (s *layerServer) createRouter() server.Router { 33 | registry := api.NewRemoteRegistry() 34 | router := server.Router{mux.NewRouter()} 35 | 36 | registry.Routes("/registry", &router) 37 | 38 | // Handler for ExpVar request 39 | router.Path("/debug/vars").Methods("GET").HandlerFunc(expvarHandler) 40 | 41 | return router 42 | } 43 | 44 | func expvarHandler(w http.ResponseWriter, r *http.Request) { 45 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 46 | fmt.Fprintf(w, "{\n") 47 | first := true 48 | expvar.Do(func(kv expvar.KeyValue) { 49 | if !first { 50 | fmt.Fprintf(w, ",\n") 51 | } 52 | first = false 53 | fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) 54 | }) 55 | fmt.Fprintf(w, "\n}\n") 56 | } 57 | 58 | func main() { 59 | port := flag.Int("p", 8888, "port on which the server will run") 60 | flag.Parse() 61 | 62 | s := NewServer() 63 | s.Start(*port) 64 | } 65 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | srv *httptest.Server 14 | ) 15 | 16 | func setupServer() { 17 | srv = httptest.NewServer(NewServer().createRouter()) 18 | } 19 | 20 | func teardownServer() { 21 | srv.Close() 22 | } 23 | 24 | func TestStatusGetSuccess(t *testing.T) { 25 | setupServer() 26 | defer teardownServer() 27 | 28 | path := srv.URL + "/registry/status" 29 | 30 | req, _ := http.NewRequest("GET", path, nil) 31 | req.Header.Add("Origin", srv.URL) 32 | resp, err := http.DefaultClient.Do(req) 33 | 34 | assert.Nil(t, err) 35 | assert.Equal(t, 200, resp.StatusCode) 36 | assert.Equal(t, srv.URL, resp.Header.Get("Access-Control-Allow-Origin"), "CORS Allow-Origin not set") 37 | } 38 | 39 | func TestAnalyzePostSuccess(t *testing.T) { 40 | setupServer() 41 | defer teardownServer() 42 | 43 | path := srv.URL + "/registry/analyze" 44 | 45 | req, _ := http.NewRequest("POST", path, strings.NewReader("{}")) 46 | req.Header.Add("Origin", srv.URL) 47 | resp, err := http.DefaultClient.Do(req) 48 | 49 | assert.Nil(t, err) 50 | assert.Equal(t, 200, resp.StatusCode) 51 | assert.Equal(t, srv.URL, resp.Header.Get("Access-Control-Allow-Origin"), "CORS Allow-Origin not set") 52 | } 53 | 54 | func TestAnalyzeGetNotFound(t *testing.T) { 55 | setupServer() 56 | defer teardownServer() 57 | 58 | path := srv.URL + "/registry/analyze" 59 | 60 | req, _ := http.NewRequest("GET", path, nil) 61 | resp, _ := http.DefaultClient.Do(req) 62 | 63 | assert.Equal(t, 404, resp.StatusCode) 64 | } 65 | 66 | func TestStatusPostNotFound(t *testing.T) { 67 | setupServer() 68 | defer teardownServer() 69 | 70 | path := srv.URL + "/registry/status" 71 | 72 | req, _ := http.NewRequest("POST", path, strings.NewReader("{}")) 73 | resp, _ := http.DefaultClient.Do(req) 74 | 75 | assert.Equal(t, 404, resp.StatusCode) 76 | } 77 | 78 | func TestBadURL(t *testing.T) { 79 | setupServer() 80 | defer teardownServer() 81 | 82 | path := srv.URL + "/registry/foo" 83 | 84 | req, _ := http.NewRequest("GET", path, nil) 85 | resp, _ := http.DefaultClient.Do(req) 86 | 87 | assert.Equal(t, 404, resp.StatusCode) 88 | } 89 | -------------------------------------------------------------------------------- /server/router.go: -------------------------------------------------------------------------------- 1 | package server // import "github.com/microscaling/imagelayers/server" 2 | 3 | import ( 4 | "expvar" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/rs/cors" 10 | ) 11 | 12 | var ( 13 | expRequests = expvar.NewInt("requests") 14 | ) 15 | 16 | type statusLoggingResponseWriter struct { 17 | http.ResponseWriter 18 | statusCode int 19 | } 20 | 21 | func (w *statusLoggingResponseWriter) WriteHeader(code int) { 22 | w.statusCode = code 23 | w.ResponseWriter.WriteHeader(code) 24 | } 25 | 26 | type Router struct { 27 | *mux.Router 28 | } 29 | 30 | type RouteMap map[string]map[string]http.HandlerFunc 31 | 32 | func (sr *Router) AddCorsRoutes(context string, routeMap RouteMap) { 33 | sr.generateRoutes(context, routeMap, func(path string, method string, wrap http.HandlerFunc) { 34 | corsHndlr := cors.New(cors.Options{ 35 | AllowedMethods: []string{"GET", "POST", "DELETE", "PUT"}, 36 | AllowCredentials: true, 37 | }) 38 | 39 | sr.Path(path).Methods("OPTIONS").Handler(corsHndlr.Handler(wrap)) 40 | sr.Path(path).Methods(method).Handler(corsHndlr.Handler(wrap)) 41 | }) 42 | } 43 | 44 | func (sr *Router) AddRoutes(context string, routeMap RouteMap) { 45 | sr.generateRoutes(context, routeMap, func(path string, method string, wrap http.HandlerFunc) { 46 | sr.Path(path).Methods(method).Handler(wrap) 47 | }) 48 | } 49 | 50 | func (sr *Router) generateRoutes(context string, routeMap RouteMap, build func(string, string, http.HandlerFunc)) { 51 | 52 | for method, routes := range routeMap { 53 | for route, fct := range routes { 54 | 55 | localMethod := method 56 | localRoute := route 57 | localFct := fct 58 | 59 | wrap := func(w http.ResponseWriter, r *http.Request) { 60 | ww := &statusLoggingResponseWriter{w, 200} 61 | 62 | expRequests.Add(1) 63 | log.Printf("Started %s %s", r.Method, r.RequestURI) 64 | 65 | localFct(ww, r) 66 | 67 | log.Printf("Completed %d", ww.statusCode) 68 | } 69 | 70 | build(context+localRoute, localMethod, wrap) 71 | 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server/router_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | var ( 13 | srv *httptest.Server 14 | ) 15 | 16 | func mockHandler(w http.ResponseWriter, r *http.Request) { 17 | } 18 | 19 | func setupServer() { 20 | router := Router{mux.NewRouter()} 21 | m := RouteMap{ 22 | "GET": { 23 | "/it": mockHandler, 24 | }, 25 | } 26 | 27 | router.AddRoutes("/test", m) 28 | router.AddCorsRoutes("/cors", m) 29 | srv = httptest.NewServer(router) 30 | } 31 | 32 | func teardownServer() { 33 | srv.Close() 34 | } 35 | 36 | func TestRoute(t *testing.T) { 37 | setupServer() 38 | defer teardownServer() 39 | 40 | path := srv.URL + "/test/it" 41 | resp, err := http.DefaultClient.Get(path) 42 | 43 | assert.Nil(t, err) 44 | assert.Equal(t, 200, resp.StatusCode) 45 | } 46 | 47 | func TestCorsRoute(t *testing.T) { 48 | setupServer() 49 | defer teardownServer() 50 | 51 | path := srv.URL + "/cors/it" 52 | 53 | req, _ := http.NewRequest("GET", path, nil) 54 | req.Header.Add("Origin", srv.URL) 55 | resp, err := http.DefaultClient.Do(req) 56 | 57 | assert.Nil(t, err) 58 | assert.Equal(t, 200, resp.StatusCode) 59 | assert.Equal(t, srv.URL, resp.Header.Get("Access-Control-Allow-Origin"), "CORS Allow-Origin not set") 60 | } 61 | --------------------------------------------------------------------------------