diff --git a/builtin/logical/postgresql/backend_test.go b/builtin/logical/postgresql/backend_test.go index ac42fee92..ae4c22656 100644 --- a/builtin/logical/postgresql/backend_test.go +++ b/builtin/logical/postgresql/backend_test.go @@ -6,14 +6,71 @@ import ( "log" "os" "reflect" + "sync" "testing" + "time" "github.com/hashicorp/vault/logical" logicaltest "github.com/hashicorp/vault/logical/testing" "github.com/lib/pq" "github.com/mitchellh/mapstructure" + "github.com/ory-am/dockertest" ) +var ( + testImagePull sync.Once +) + +func prepareTestContainer(t *testing.T, s logical.Storage, b logical.Backend) (cid dockertest.ContainerID, retURL string) { + if os.Getenv("PG_URL") != "" { + return "", os.Getenv("PG_URL") + } + + // Without this the checks for whether the container has started seem to + // never actually pass. There's really no reason to expose the test + // containers, so don't. + dockertest.BindDockerToLocalhost = "yep" + + testImagePull.Do(func() { + dockertest.Pull("postgres") + }) + + cid, connErr := dockertest.ConnectToPostgreSQL(60, 500*time.Millisecond, func(connURL string) bool { + // This will cause a validation to run + resp, err := b.HandleRequest(&logical.Request{ + Storage: s, + Operation: logical.UpdateOperation, + Path: "config/connection", + Data: map[string]interface{}{ + "connection_url": connURL, + }, + }) + if err != nil || (resp != nil && resp.IsError()) { + // It's likely not up and running yet, so return false and try again + return false + } + if resp == nil { + t.Fatal("expected warning") + } + + retURL = connURL + return true + }) + + if connErr != nil { + t.Fatalf("could not connect to database: %v", connErr) + } + + return +} + +func cleanupTestContainer(t *testing.T, cid dockertest.ContainerID) { + err := cid.KillRemove() + if err != nil { + t.Fatal(err) + } +} + func TestBackend_config_connection(t *testing.T) { var resp *logical.Response var err error @@ -55,43 +112,51 @@ func TestBackend_config_connection(t *testing.T) { } func TestBackend_basic(t *testing.T) { - b, _ := Factory(logical.TestBackendConfig()) - - d1 := map[string]interface{}{ - "connection_url": os.Getenv("PG_URL"), + config := logical.TestBackendConfig() + config.StorageView = &logical.InmemStorage{} + b, err := Factory(config) + if err != nil { + t.Fatal(err) } - d2 := map[string]interface{}{ - "value": os.Getenv("PG_URL"), + + cid, connURL := prepareTestContainer(t, config.StorageView, b) + if cid != "" { + defer cleanupTestContainer(t, cid) + } + connData := map[string]interface{}{ + "connection_url": connURL, } logicaltest.Test(t, logicaltest.TestCase{ - AcceptanceTest: true, - PreCheck: func() { testAccPreCheck(t) }, - Backend: b, + Backend: b, Steps: []logicaltest.TestStep{ - testAccStepConfig(t, d1, false), + testAccStepConfig(t, connData, false), testAccStepRole(t), - testAccStepReadCreds(t, b, "web"), - testAccStepConfig(t, d2, false), - testAccStepRole(t), - testAccStepReadCreds(t, b, "web"), + testAccStepReadCreds(t, b, "web", connURL), }, }) - } func TestBackend_roleCrud(t *testing.T) { - b, _ := Factory(logical.TestBackendConfig()) - d := map[string]interface{}{ - "connection_url": os.Getenv("PG_URL"), + config := logical.TestBackendConfig() + config.StorageView = &logical.InmemStorage{} + b, err := Factory(config) + if err != nil { + t.Fatal(err) + } + + cid, connURL := prepareTestContainer(t, config.StorageView, b) + if cid != "" { + defer cleanupTestContainer(t, cid) + } + connData := map[string]interface{}{ + "connection_url": connURL, } logicaltest.Test(t, logicaltest.TestCase{ - AcceptanceTest: true, - PreCheck: func() { testAccPreCheck(t) }, - Backend: b, + Backend: b, Steps: []logicaltest.TestStep{ - testAccStepConfig(t, d, false), + testAccStepConfig(t, connData, false), testAccStepRole(t), testAccStepReadRole(t, "web", testRole), testAccStepDeleteRole(t, "web"), @@ -100,39 +165,6 @@ func TestBackend_roleCrud(t *testing.T) { }) } -func TestBackend_configConnection(t *testing.T) { - b, _ := Factory(logical.TestBackendConfig()) - d1 := map[string]interface{}{ - "value": os.Getenv("PG_URL"), - } - d2 := map[string]interface{}{ - "connection_url": os.Getenv("PG_URL"), - } - d3 := map[string]interface{}{ - "value": os.Getenv("PG_URL"), - "connection_url": os.Getenv("PG_URL"), - } - d4 := map[string]interface{}{} - - logicaltest.Test(t, logicaltest.TestCase{ - AcceptanceTest: true, - PreCheck: func() { testAccPreCheck(t) }, - Backend: b, - Steps: []logicaltest.TestStep{ - testAccStepConfig(t, d1, false), - testAccStepConfig(t, d2, false), - testAccStepConfig(t, d3, false), - testAccStepConfig(t, d4, true), - }, - }) -} - -func testAccPreCheck(t *testing.T) { - if v := os.Getenv("PG_URL"); v == "" { - t.Fatal("PG_URL must be set for acceptance tests") - } -} - func testAccStepConfig(t *testing.T, d map[string]interface{}, expectError bool) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, @@ -154,8 +186,8 @@ func testAccStepConfig(t *testing.T, d map[string]interface{}, expectError bool) return fmt.Errorf("expected error, but write succeeded.") } return nil - } else if resp != nil { - return fmt.Errorf("response should be nil") + } else if resp != nil && resp.IsError() { + return fmt.Errorf("got an error response: %v", resp.Error()) } return nil }, @@ -179,7 +211,7 @@ func testAccStepDeleteRole(t *testing.T, n string) logicaltest.TestStep { } } -func testAccStepReadCreds(t *testing.T, b logical.Backend, name string) logicaltest.TestStep { +func testAccStepReadCreds(t *testing.T, b logical.Backend, name string, connURL string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "creds/" + name, @@ -193,7 +225,7 @@ func testAccStepReadCreds(t *testing.T, b logical.Backend, name string) logicalt } log.Printf("[WARN] Generated credentials: %v", d) - conn, err := pq.ParseURL(os.Getenv("PG_URL")) + conn, err := pq.ParseURL(connURL) if err != nil { t.Fatal(err) } diff --git a/builtin/logical/postgresql/path_config_connection.go b/builtin/logical/postgresql/path_config_connection.go index e70ab97cf..2617c0391 100644 --- a/builtin/logical/postgresql/path_config_connection.go +++ b/builtin/logical/postgresql/path_config_connection.go @@ -18,16 +18,19 @@ func pathConfigConnection(b *backend) *framework.Path { Type: framework.TypeString, Description: "DB connection string", }, + "value": &framework.FieldSchema{ Type: framework.TypeString, Description: `DB connection string. Use 'connection_url' instead. This will be deprecated.`, }, + "verify_connection": &framework.FieldSchema{ Type: framework.TypeBool, Default: true, Description: `If set, connection_url is verified by actually connecting to the database`, }, + "max_open_connections": &framework.FieldSchema{ Type: framework.TypeInt, Description: `Maximum number of open connections to the database; @@ -35,7 +38,6 @@ a zero uses the default value of two and a negative value means unlimited`, }, - // Implementation note: "max_idle_connections": &framework.FieldSchema{ Type: framework.TypeInt, Description: `Maximum number of idle connections to the database; diff --git a/builtin/logical/postgresql/secret_creds.go b/builtin/logical/postgresql/secret_creds.go index 1cd0f2d19..5b78faaa6 100644 --- a/builtin/logical/postgresql/secret_creds.go +++ b/builtin/logical/postgresql/secret_creds.go @@ -97,6 +97,18 @@ func (b *backend) secretCredsRevoke( return nil, err } + // Check if the role exists + var exists bool + query := fmt.Sprintf("SELECT exists (SELECT rolname FROM pg_roles WHERE rolname='%s');", username) + err = db.QueryRow(query).Scan(&exists) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + + if exists == false { + return nil, nil + } + // Query for permissions; we need to revoke permissions before we can drop // the role // This isn't done in a transaction because even if we fail along the way, diff --git a/logical/response.go b/logical/response.go index 033524f84..02566d99b 100644 --- a/logical/response.go +++ b/logical/response.go @@ -1,6 +1,7 @@ package logical import ( + "errors" "fmt" "reflect" "time" @@ -154,6 +155,19 @@ func (r *Response) IsError() bool { return r != nil && len(r.Data) == 1 && r.Data["error"] != nil } +func (r *Response) Error() error { + if !r.IsError() { + return nil + } + switch r.Data["error"].(type) { + case string: + return errors.New(r.Data["error"].(string)) + case error: + return r.Data["error"].(error) + } + return nil +} + // HelpResponse is used to format a help response func HelpResponse(text string, seeAlso []string) *Response { return &Response{ diff --git a/vendor/camlistore.org/COPYING b/vendor/camlistore.org/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/camlistore.org/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/camlistore.org/pkg/netutil/ident.go b/vendor/camlistore.org/pkg/netutil/ident.go new file mode 100644 index 000000000..3866f8c1e --- /dev/null +++ b/vendor/camlistore.org/pkg/netutil/ident.go @@ -0,0 +1,275 @@ +/* +Copyright 2011 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package netutil identifies the system userid responsible for +// localhost TCP connections. +package netutil // import "camlistore.org/pkg/netutil" + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "os" + "os/exec" + "os/user" + "regexp" + "runtime" + "strconv" + "strings" +) + +var ( + ErrNotFound = errors.New("netutil: connection not found") + ErrUnsupportedOS = errors.New("netutil: not implemented on this operating system") +) + +// ConnUserid returns the uid that owns the given localhost connection. +// The returned error is ErrNotFound if the connection wasn't found. +func ConnUserid(conn net.Conn) (uid int, err error) { + return AddrPairUserid(conn.LocalAddr(), conn.RemoteAddr()) +} + +// HostPortToIP parses a host:port to a TCPAddr without resolving names. +// If given a context IP, it will resolve localhost to match the context's IP family. +func HostPortToIP(hostport string, ctx *net.TCPAddr) (hostaddr *net.TCPAddr, err error) { + host, port, err := net.SplitHostPort(hostport) + if err != nil { + return nil, err + } + iport, err := strconv.Atoi(port) + if err != nil || iport < 0 || iport > 0xFFFF { + return nil, fmt.Errorf("invalid port %d", iport) + } + var addr net.IP + if ctx != nil && host == "localhost" { + if ctx.IP.To4() != nil { + addr = net.IPv4(127, 0, 0, 1) + } else { + addr = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + } + } else if addr = net.ParseIP(host); addr == nil { + return nil, fmt.Errorf("could not parse IP %s", host) + } + + return &net.TCPAddr{IP: addr, Port: iport}, nil +} + +// AddrPairUserid returns the local userid who owns the TCP connection +// given by the local and remote ip:port (lipport and ripport, +// respectively). Returns ErrNotFound for the error if the TCP connection +// isn't found. +func AddrPairUserid(local, remote net.Addr) (uid int, err error) { + lAddr, lOk := local.(*net.TCPAddr) + rAddr, rOk := remote.(*net.TCPAddr) + if !(lOk && rOk) { + return -1, fmt.Errorf("netutil: Could not convert Addr to TCPAddr.") + } + + localv4 := (lAddr.IP.To4() != nil) + remotev4 := (rAddr.IP.To4() != nil) + if localv4 != remotev4 { + return -1, fmt.Errorf("netutil: address pairs of different families; localv4=%v, remotev4=%v", + localv4, remotev4) + } + + switch runtime.GOOS { + case "darwin": + return uidFromLsof(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port) + case "freebsd": + return uidFromSockstat(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port) + case "linux": + file := "/proc/net/tcp" + if !localv4 { + file = "/proc/net/tcp6" + } + f, err := os.Open(file) + if err != nil { + return -1, fmt.Errorf("Error opening %s: %v", file, err) + } + defer f.Close() + return uidFromProcReader(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port, f) + } + return 0, ErrUnsupportedOS +} + +func toLinuxIPv4Order(b []byte) []byte { + binary.BigEndian.PutUint32(b, binary.LittleEndian.Uint32(b)) + return b +} + +func toLinuxIPv6Order(b []byte) []byte { + for i := 0; i < 16; i += 4 { + sb := b[i : i+4] + binary.BigEndian.PutUint32(sb, binary.LittleEndian.Uint32(sb)) + } + return b +} + +type maybeBrackets net.IP + +func (p maybeBrackets) String() string { + s := net.IP(p).String() + if strings.Contains(s, ":") { + return "[" + s + "]" + } + return s +} + +// Changed by tests. +var uidFromUsername = uidFromUsernameFn + +func uidFromUsernameFn(username string) (uid int, err error) { + if uid := os.Getuid(); uid != 0 && username == os.Getenv("USER") { + return uid, nil + } + u, err := user.Lookup(username) + if err == nil { + uid, err := strconv.Atoi(u.Uid) + return uid, err + } + return 0, err +} + +func uidFromLsof(lip net.IP, lport int, rip net.IP, rport int) (uid int, err error) { + seek := fmt.Sprintf("%s:%d->%s:%d", maybeBrackets(lip), lport, maybeBrackets(rip), rport) + seekb := []byte(seek) + if _, err = exec.LookPath("lsof"); err != nil { + return + } + cmd := exec.Command("lsof", + "-b", // avoid system calls that could block + "-w", // and don't warn about cases where -b fails + "-n", // don't resolve network names + "-P", // don't resolve network ports, + // TODO(bradfitz): pass down the uid we care about, then do: ? + //"-a", // AND the following together: + // "-u", strconv.Itoa(uid) // just this uid + "-itcp") // we only care about TCP connections + stdout, err := cmd.StdoutPipe() + if err != nil { + return + } + defer cmd.Wait() + defer stdout.Close() + err = cmd.Start() + if err != nil { + return + } + defer cmd.Process.Kill() + br := bufio.NewReader(stdout) + for { + line, err := br.ReadSlice('\n') + if err == io.EOF { + break + } + if err != nil { + return -1, err + } + if !bytes.Contains(line, seekb) { + continue + } + // SystemUIS 276 bradfitz 15u IPv4 0xffffff801a7c74e0 0t0 TCP 127.0.0.1:56718->127.0.0.1:5204 (ESTABLISHED) + f := bytes.Fields(line) + if len(f) < 8 { + continue + } + username := string(f[2]) + return uidFromUsername(username) + } + return -1, ErrNotFound + +} + +func uidFromSockstat(lip net.IP, lport int, rip net.IP, rport int) (int, error) { + cmd := exec.Command("sockstat", "-Ptcp") + stdout, err := cmd.StdoutPipe() + if err != nil { + return -1, err + } + defer cmd.Wait() + defer stdout.Close() + err = cmd.Start() + if err != nil { + return -1, err + } + defer cmd.Process.Kill() + + return uidFromSockstatReader(lip, lport, rip, rport, stdout) +} + +func uidFromSockstatReader(lip net.IP, lport int, rip net.IP, rport int, r io.Reader) (int, error) { + pat, err := regexp.Compile(fmt.Sprintf(`^([^ ]+).*%s:%d *%s:%d$`, + lip.String(), lport, rip.String(), rport)) + if err != nil { + return -1, err + } + scanner := bufio.NewScanner(r) + for scanner.Scan() { + l := scanner.Text() + m := pat.FindStringSubmatch(l) + if len(m) == 2 { + return uidFromUsername(m[1]) + } + } + + if err := scanner.Err(); err != nil { + return -1, err + } + + return -1, ErrNotFound +} + +func uidFromProcReader(lip net.IP, lport int, rip net.IP, rport int, r io.Reader) (uid int, err error) { + buf := bufio.NewReader(r) + + localHex := "" + remoteHex := "" + ipv4 := lip.To4() != nil + if ipv4 { + // In the kernel, the port is run through ntohs(), and + // the inet_request_socket in + // include/net/inet_socket.h says the "loc_addr" and + // "rmt_addr" fields are __be32, but get_openreq4's + // printf of them is raw, without byte order + // converstion. + localHex = fmt.Sprintf("%08X:%04X", toLinuxIPv4Order([]byte(lip.To4())), lport) + remoteHex = fmt.Sprintf("%08X:%04X", toLinuxIPv4Order([]byte(rip.To4())), rport) + } else { + localHex = fmt.Sprintf("%032X:%04X", toLinuxIPv6Order([]byte(lip.To16())), lport) + remoteHex = fmt.Sprintf("%032X:%04X", toLinuxIPv6Order([]byte(rip.To16())), rport) + } + + for { + line, err := buf.ReadString('\n') + if err != nil { + return -1, ErrNotFound + } + parts := strings.Fields(strings.TrimSpace(line)) + if len(parts) < 8 { + continue + } + // log.Printf("parts[1] = %q; localHex = %q", parts[1], localHex) + if parts[1] == localHex && parts[2] == remoteHex { + uid, err = strconv.Atoi(parts[7]) + return uid, err + } + } + panic("unreachable") +} diff --git a/vendor/camlistore.org/pkg/netutil/netutil.go b/vendor/camlistore.org/pkg/netutil/netutil.go new file mode 100644 index 000000000..0add9320d --- /dev/null +++ b/vendor/camlistore.org/pkg/netutil/netutil.go @@ -0,0 +1,141 @@ +/* +Copyright 2014 The Camlistore Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package netutil + +import ( + "errors" + "fmt" + "net" + "net/url" + "strings" + "time" +) + +// AwaitReachable tries to make a TCP connection to addr regularly. +// It returns an error if it's unable to make a connection before maxWait. +func AwaitReachable(addr string, maxWait time.Duration) error { + done := time.Now().Add(maxWait) + for time.Now().Before(done) { + c, err := net.Dial("tcp", addr) + if err == nil { + c.Close() + return nil + } + time.Sleep(100 * time.Millisecond) + } + return fmt.Errorf("%v unreachable for %v", addr, maxWait) +} + +// HostPort takes a urlStr string URL, and returns a host:port string suitable +// to passing to net.Dial, with the port set as the scheme's default port if +// absent. +func HostPort(urlStr string) (string, error) { + // TODO: rename this function to URLHostPort instead, like + // ListenHostPort below. + u, err := url.Parse(urlStr) + if err != nil { + return "", fmt.Errorf("could not parse %q as a url: %v", urlStr, err) + } + if u.Scheme == "" { + return "", fmt.Errorf("url %q has no scheme", urlStr) + } + hostPort := u.Host + if hostPort == "" || strings.HasPrefix(hostPort, ":") { + return "", fmt.Errorf("url %q has no host", urlStr) + } + idx := strings.Index(hostPort, "]") + if idx == -1 { + idx = 0 + } + if !strings.Contains(hostPort[idx:], ":") { + if u.Scheme == "https" { + hostPort += ":443" + } else { + hostPort += ":80" + } + } + return hostPort, nil +} + +// ListenHostPort maps a listen address into a host:port string. +// If the host part in listenAddr is empty or 0.0.0.0, localhost +// is used instead. +func ListenHostPort(listenAddr string) (string, error) { + hp := listenAddr + if strings.HasPrefix(hp, ":") { + hp = "localhost" + hp + } else if strings.HasPrefix(hp, "0.0.0.0:") { + hp = "localhost:" + hp[len("0.0.0.0:"):] + } + if _, _, err := net.SplitHostPort(hp); err != nil { + return "", err + } + return hp, nil +} + +// ListenOnLocalRandomPort returns a TCP listener on a random +// localhost port. +func ListenOnLocalRandomPort() (net.Listener, error) { + ip, err := Localhost() + if err != nil { + return nil, err + } + return net.ListenTCP("tcp", &net.TCPAddr{IP: ip, Port: 0}) +} + +// Localhost returns the first address found when +// doing a lookup of "localhost". If not successful, +// it looks for an ip on the loopback interfaces. +func Localhost() (net.IP, error) { + if ip := localhostLookup(); ip != nil { + return ip, nil + } + if ip := loopbackIP(); ip != nil { + return ip, nil + } + return nil, errors.New("No loopback ip found.") +} + +// localhostLookup looks for a loopback IP by resolving localhost. +func localhostLookup() net.IP { + if ips, err := net.LookupIP("localhost"); err == nil && len(ips) > 0 { + return ips[0] + } + return nil +} + +// loopbackIP returns the first loopback IP address sniffing network +// interfaces or nil if none is found. +func loopbackIP() net.IP { + interfaces, err := net.Interfaces() + if err != nil { + return nil + } + for _, inf := range interfaces { + const flagUpLoopback = net.FlagUp | net.FlagLoopback + if inf.Flags&flagUpLoopback == flagUpLoopback { + addrs, _ := inf.Addrs() + for _, addr := range addrs { + ip, _, err := net.ParseCIDR(addr.String()) + if err == nil && ip.IsLoopback() { + return ip + } + } + } + } + return nil +} diff --git a/vendor/github.com/ory-am/dockertest/LICENSE b/vendor/github.com/ory-am/dockertest/LICENSE new file mode 100644 index 000000000..1f8f689ce --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 The Camlistore Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/ory-am/dockertest/README.md b/vendor/github.com/ory-am/dockertest/README.md new file mode 100644 index 000000000..7569447d8 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/README.md @@ -0,0 +1,258 @@ +# [ory.am](https://ory.am)/dockertest + +[![Build Status](https://travis-ci.org/ory-am/dockertest.svg)](https://travis-ci.org/ory-am/dockertest?branch=master) +[![Coverage Status](https://coveralls.io/repos/ory-am/dockertest/badge.svg?branch=master&service=github)](https://coveralls.io/github/ory-am/dockertest?branch=master) + +Use Docker to run your Go language integration tests against third party services on **Microsoft Windows, Mac OSX and Linux**! +Dockertest uses [docker-machine](https://docs.docker.com/machine/) (aka [Docker Toolbox](https://www.docker.com/toolbox)) to spin up images on Windows and Mac OSX as well. +Dockertest is based on [docker.go](https://github.com/camlistore/camlistore/blob/master/pkg/test/dockertest/docker.go) +from [camlistore](https://github.com/camlistore/camlistore). + +This fork detects automatically, if [Docker Toolbox](https://www.docker.com/toolbox) +is installed. If it is, Docker integration on Windows and Mac OSX can be used without any additional work. +To avoid port collisions when using docker-machine, Dockertest chooses a random port to bind the requested image. + +Dockertest ships with support for these backends: +* PostgreSQL +* MySQL +* MongoDB +* NSQ +* Redis +* Elastic Search +* RethinkDB +* RabbitMQ +* Mockserver +* ActiveMQ + + + +**Table of Contents** + +- [Why should I use Dockertest?](#why-should-i-use-dockertest) +- [Installing and using Dockertest](#installing-and-using-dockertest) + - [Start a container](#start-a-container) +- [Write awesome tests](#write-awesome-tests) + - [Setting up Travis-CI](#setting-up-travis-ci) +- [Troubleshoot & FAQ](#troubleshoot-&-faq) + - [I want to use a specific image version](#i-want-to-use-a-specific-image-version) + - [My build is broken!](#my-build-is-broken) + - [Out of disk space](#out-of-disk-space) + - [Removing old containers](#removing-old-containers) + - [Customized database] (#Customized-database) + + + +## Why should I use Dockertest? + +When developing applications, it is often necessary to use services that talk to a database system. +Unit Testing these services can be cumbersome because mocking database/DBAL is strenuous. Making slight changes to the +schema implies rewriting at least some, if not all of the mocks. The same goes for API changes in the DBAL. +To avoid this, it is smarter to test these specific services against a real database that is destroyed after testing. +Docker is the perfect system for running unit tests as you can spin up containers in a few seconds and kill them when +the test completes. The Dockertest library provides easy to use commands for spinning up Docker containers and using +them for your tests. + +## Installing and using Dockertest + +Using Dockertest is straightforward and simple. Check the [releases tab](https://github.com/ory-am/dockertest/releases) +for available releases. + +To install dockertest, run + +``` +go get gopkg.in/ory-am/dockertest.vX +``` + +where `X` is your desired version. For example: + +``` +go get gopkg.in/ory-am/dockertest.v2 +``` + +**Note:** +When using the Docker Toolbox (Windows / OSX), make sure that the VM is started by running `docker-machine start default`. + +### Start a container + +```go +package main + +import ( + "gopkg.in/ory-am/dockertest.v2" + "gopkg.in/mgo.v2" + "time" +) + +func main() { + var db *mgo.Session + c, err := dockertest.ConnectToMongoDB(15, time.Millisecond*500, func(url string) bool { + // This callback function checks if the image's process is responsive. + // Sometimes, docker images are booted but the process (in this case MongoDB) is still doing maintenance + // before being fully responsive which might cause issues like "TCP Connection reset by peer". + var err error + db, err = mgo.Dial(url) + if err != nil { + return false + } + + // Sometimes, dialing the database is not enough because the port is already open but the process is not responsive. + // Most database conenctors implement a ping function which can be used to test if the process is responsive. + // Alternatively, you could execute a query to see if an error occurs or not. + return db.Ping() == nil + }) + + if err != nil { + log.Fatalf("Could not connect to database: %s", err) + } + + // Close db connection and kill the container when we leave this function body. + defer db.Close() + defer c.KillRemove() + + // The image is now responsive. +} +``` + +You can start PostgreSQL and MySQL in a similar fashion. + +It is also possible to start a custom container (in this example, a RabbitMQ container): + +```go + c, ip, port, err := dockertest.SetupCustomContainer("rabbitmq", 5672, 10*time.Second) + if err != nil { + log.Fatalf("Could not setup container: %s", err + } + defer c.KillRemove() + + err = dockertest.ConnectToCustomContainer(fmt.Sprintf("%v:%v", ip, port), 15, time.Millisecond*500, func(url string) bool { + amqp, err := amqp.Dial(fmt.Sprintf("amqp://%v", url)) + if err != nil { + return false + } + defer amqp.Close() + return true + }) + + ... +``` + +## Write awesome tests + +It is a good idea to start up the container only once when running tests. + +```go + +import ( + "fmt" + "testing" + "log" + "os" + + "database/sql" + _ "github.com/lib/pq" + "gopkg.in/ory-am/dockertest.v2" +) + +var db *sql.DB + +func TestMain(m *testing.M) { + c, err := dockertest.ConnectToPostgreSQL(15, time.Second, func(url string) bool { + // Check if postgres is responsive... + var err error + db, err = sql.Open("postgres", url) + if err != nil { + return false + } + return db.Ping() == nil + }) + if err != nil { + log.Fatalf("Could not connect to database: %s", err) + } + + // Execute tasks like setting up schemata. + + // Run tests + result := m.Run() + + // Close database connection. + db.Close() + + // Clean up image. + c.KillRemove() + + // Exit tests. + os.Exit(result) +} + +func TestFunction(t *testing.T) { + // db.Exec(... +} +``` + +### Setting up Travis-CI + +You can run the Docker integration on Travis easily: + +```yml +# Sudo is required for docker +sudo: required + +# Enable docker +services: + - docker + +# In Travis, we need to bind to 127.0.0.1 in order to get a working connection. This environment variable +# tells dockertest to do that. +env: + - DOCKERTEST_BIND_LOCALHOST=true +``` + +## Troubleshoot & FAQ + +### I need to use a specific container version for XYZ + +You can specify a container version by setting environment variables or globals. For more information, check [vars.go](vars.go). + +### My build is broken! + +With v2, we removed all `Open*` methods to reduce duplicate code, unnecessary dependencies and make maintenance easier. +If you relied on these, run `go get gopkg.in/ory-am/dockertest.v1` and replace +`import "github.com/ory-am/dockertest"` with `import "gopkg.in/ory-am/dockertest.v1"`. + +### Out of disk space + +Try cleaning up the images with [docker-cleanup-volumes](https://github.com/chadoe/docker-cleanup-volumes). + +### Removing old containers + +Sometimes container clean up fails. Check out +[this stackoverflow question](http://stackoverflow.com/questions/21398087/how-to-delete-dockers-images) on how to fix this. + +### Customized database + +I am using postgres (or mysql) driver, how do I use customized database instead of default one? +You can alleviate this helper function to do that, see testcase or example below: + +```go + +func TestMain(m *testing.M) { + if c, err := dockertest.ConnectToPostgreSQL(15, time.Second, func(url string) bool { + customizedDB := "cherry" // here I am connecting cherry database + newURL, err := SetUpPostgreDatabase(customizedDB, url) + + // or use SetUpMysqlDatabase for mysql driver + + if err != nil { + log.Fatal(err) + } + db, err := sql.Open("postgres", newURL) + if err != nil { + return false + } + return db.Ping() == nil + }); err != nil { + log.Fatal(err) + } +``` + +*Thanks to our sponsors: Ory GmbH & Imarum GmbH* diff --git a/vendor/github.com/ory-am/dockertest/activemq.go b/vendor/github.com/ory-am/dockertest/activemq.go new file mode 100644 index 000000000..22c33206d --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/activemq.go @@ -0,0 +1,43 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupActiveMQContainer sets up a real ActiveMQ instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupActiveMQContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 61613) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(ActiveMQImageName, port, 10*time.Second, func() (string, error) { + res, err := run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, ActiveMQImageName) + return res, err + }) + return +} + +// ConnectToActiveMQ starts a ActiveMQ image and passes the amqp url to the connector callback. +// The url will match the ip:port pattern (e.g. 123.123.123.123:4241) +func ConnectToActiveMQ(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupActiveMQContainer() + if err != nil { + return c, fmt.Errorf("Could not set up ActiveMQ container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up ActiveMQ container.") +} diff --git a/vendor/github.com/ory-am/dockertest/container.go b/vendor/github.com/ory-am/dockertest/container.go new file mode 100644 index 000000000..0c939d454 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/container.go @@ -0,0 +1,65 @@ +package dockertest + +import ( + "camlistore.org/pkg/netutil" + "fmt" + "os/exec" + "strings" + "time" +) + +// ContainerID represents a container and offers methods like Kill or IP. +type ContainerID string + +// IP retrieves the container's IP address. +func (c ContainerID) IP() (string, error) { + return IP(string(c)) +} + +// Kill runs "docker kill" on the container. +func (c ContainerID) Kill() error { + return KillContainer(string(c)) +} + +// Remove runs "docker rm" on the container +func (c ContainerID) Remove() error { + if Debug || c == "nil" { + return nil + } + return runDockerCommand("docker", "rm", "-v", string(c)).Run() +} + +// KillRemove calls Kill on the container, and then Remove if there was +// no error. +func (c ContainerID) KillRemove() error { + if err := c.Kill(); err != nil { + return err + } + return c.Remove() +} + +// lookup retrieves the ip address of the container, and tries to reach +// before timeout the tcp address at this ip and given port. +func (c ContainerID) lookup(ports []int, timeout time.Duration) (ip string, err error) { + if DockerMachineAvailable { + var out []byte + out, err = exec.Command("docker-machine", "ip", DockerMachineName).Output() + ip = strings.TrimSpace(string(out)) + } else if BindDockerToLocalhost != "" { + ip = "127.0.0.1" + } else { + ip, err = c.IP() + } + if err != nil { + err = fmt.Errorf("error getting IP: %v", err) + return + } + for _, port := range ports { + addr := fmt.Sprintf("%s:%d", ip, port) + err = netutil.AwaitReachable(addr, timeout) + if err != nil { + return + } + } + return +} diff --git a/vendor/github.com/ory-am/dockertest/custom.go b/vendor/github.com/ory-am/dockertest/custom.go new file mode 100644 index 000000000..84dcb3715 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/custom.go @@ -0,0 +1,39 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupCustomContainer sets up a real an instance of the given image for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupCustomContainer(imageName string, exposedPort int, timeOut time.Duration, extraDockerArgs ...string) (c ContainerID, ip string, localPort int, err error) { + localPort = RandomPort() + forward := fmt.Sprintf("%d:%d", localPort, exposedPort) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(imageName, localPort, timeOut, func() (string, error) { + args := make([]string, 0, len(extraDockerArgs)+7) + args = append(args, "--name", GenerateContainerID(), "-d", "-P", "-p", forward) + args = append(args, extraDockerArgs...) + args = append(args, imageName) + return run(args...) + }) + return +} + +// ConnectToCustomContainer attempts to connect to a custom container until successful or the maximum number of tries is reached. +func ConnectToCustomContainer(url string, tries int, delay time.Duration, connector func(url string) bool) error { + for try := 0; try <= tries; try++ { + time.Sleep(delay) + if connector(url) { + return nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return errors.New("Could not set up custom container.") +} diff --git a/vendor/github.com/ory-am/dockertest/doc.go b/vendor/github.com/ory-am/dockertest/doc.go new file mode 100644 index 000000000..127838ba0 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/doc.go @@ -0,0 +1,5 @@ +// Package dockertest contains helper functions for setting up and tearing down docker containers to aid in testing. +// dockertest supports spinning up MySQL, PostgreSQL and MongoDB out of the box. +// +// Dockertest provides two environment variables +package dockertest diff --git a/vendor/github.com/ory-am/dockertest/docker.go b/vendor/github.com/ory-am/dockertest/docker.go new file mode 100644 index 000000000..3c428b088 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/docker.go @@ -0,0 +1,260 @@ +package dockertest + +/* +Copyright 2014 The Camlistore Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log" + "math/rand" + "os/exec" + "regexp" + "strings" + "time" + + // Import postgres driver + _ "github.com/lib/pq" + "github.com/pborman/uuid" +) + +/// runLongTest checks all the conditions for running a docker container +// based on image. +func runLongTest(image string) error { + DockerMachineAvailable = false + if haveDockerMachine() { + DockerMachineAvailable = true + if !startDockerMachine() { + log.Printf(`Starting docker machine "%s" failed. +This could be because the image is already running or because the image does not exist. +Tests will fail if the image does not exist.`, DockerMachineName) + } + } else if !haveDocker() { + return errors.New("Neither 'docker' nor 'docker-machine' available on this system.") + } + if ok, err := HaveImage(image); !ok || err != nil { + if err != nil { + return fmt.Errorf("Error checking for docker image %s: %v", image, err) + } + log.Printf("Pulling docker image %s ...", image) + if err := Pull(image); err != nil { + return fmt.Errorf("Error pulling %s: %v", image, err) + } + } + return nil +} + +func runDockerCommand(command string, args ...string) *exec.Cmd { + if DockerMachineAvailable { + command = "/usr/local/bin/" + strings.Join(append([]string{command}, args...), " ") + cmd := exec.Command("docker-machine", "ssh", DockerMachineName, command) + return cmd + } + return exec.Command(command, args...) +} + +// haveDockerMachine returns whether the "docker" command was found. +func haveDockerMachine() bool { + _, err := exec.LookPath("docker-machine") + return err == nil +} + +// startDockerMachine starts the docker machine and returns false if the command failed to execute +func startDockerMachine() bool { + _, err := exec.Command("docker-machine", "start", DockerMachineName).Output() + return err == nil +} + +// haveDocker returns whether the "docker" command was found. +func haveDocker() bool { + _, err := exec.LookPath("docker") + return err == nil +} + +type dockerImage struct { + repo string + tag string +} + +type dockerImageList []dockerImage + +func (l dockerImageList) contains(repo string, tag string) bool { + if tag == "" { + tag = "latest" + } + for _, image := range l { + if image.repo == repo && image.tag == tag { + return true + } + } + return false +} + +func parseDockerImagesOutput(data []byte) (images dockerImageList) { + lines := strings.Split(string(data), "\n") + if len(lines) < 2 { + return + } + + // skip first line with columns names + images = make(dockerImageList, 0, len(lines)-1) + for _, line := range lines[1:] { + cols := strings.Fields(line) + if len(cols) < 2 { + continue + } + + image := dockerImage{ + repo: cols[0], + tag: cols[1], + } + images = append(images, image) + } + + return +} + +func parseImageName(name string) (repo string, tag string) { + if fields := strings.SplitN(name, ":", 2); len(fields) == 2 { + repo, tag = fields[0], fields[1] + } else { + repo = name + } + return +} + +// HaveImage reports if docker have image 'name'. +func HaveImage(name string) (bool, error) { + out, err := runDockerCommand("docker", "images", "--no-trunc").Output() + if err != nil { + return false, err + } + repo, tag := parseImageName(name) + images := parseDockerImagesOutput(out) + return images.contains(repo, tag), nil +} + +func run(args ...string) (containerID string, err error) { + var stdout, stderr bytes.Buffer + validID := regexp.MustCompile(`^([a-zA-Z0-9]+)$`) + cmd := runDockerCommand("docker", append([]string{"run"}, args...)...) + + cmd.Stdout, cmd.Stderr = &stdout, &stderr + if err = cmd.Run(); err != nil { + err = fmt.Errorf("Error running docker\nStdOut: %s\nStdErr: %s\nError: %v\n\n", stdout.String(), stderr.String(), err) + return + } + containerID = strings.TrimSpace(string(stdout.String())) + if !validID.MatchString(containerID) { + return "", fmt.Errorf("Error running docker: %s", containerID) + } + if containerID == "" { + return "", errors.New("Unexpected empty output from `docker run`") + } + return containerID, nil +} + +// KillContainer runs docker kill on a container. +func KillContainer(container string) error { + if container != "" { + return runDockerCommand("docker", "kill", container).Run() + } + return nil +} + +// Pull retrieves the docker image with 'docker pull'. +func Pull(image string) error { + out, err := runDockerCommand("docker", "pull", image).CombinedOutput() + if err != nil { + err = fmt.Errorf("%v: %s", err, out) + } + return err +} + +// IP returns the IP address of the container. +func IP(containerID string) (string, error) { + out, err := runDockerCommand("docker", "inspect", containerID).Output() + if err != nil { + return "", err + } + type networkSettings struct { + IPAddress string + } + type container struct { + NetworkSettings networkSettings + } + var c []container + if err := json.NewDecoder(bytes.NewReader(out)).Decode(&c); err != nil { + return "", err + } + if len(c) == 0 { + return "", errors.New("no output from docker inspect") + } + if ip := c[0].NetworkSettings.IPAddress; ip != "" { + return ip, nil + } + return "", errors.New("could not find an IP. Not running?") +} + +// SetupMultiportContainer sets up a container, using the start function to run the given image. +// It also looks up the IP address of the container, and tests this address with the given +// ports and timeout. It returns the container ID and its IP address, or makes the test +// fail on error. +func SetupMultiportContainer(image string, ports []int, timeout time.Duration, start func() (string, error)) (c ContainerID, ip string, err error) { + err = runLongTest(image) + if err != nil { + return "", "", err + } + + containerID, err := start() + if err != nil { + return "", "", err + } + + c = ContainerID(containerID) + ip, err = c.lookup(ports, timeout) + if err != nil { + c.KillRemove() + return "", "", err + } + return c, ip, nil +} + +// SetupContainer sets up a container, using the start function to run the given image. +// It also looks up the IP address of the container, and tests this address with the given +// port and timeout. It returns the container ID and its IP address, or makes the test +// fail on error. +func SetupContainer(image string, port int, timeout time.Duration, start func() (string, error)) (c ContainerID, ip string, err error) { + return SetupMultiportContainer(image, []int{port}, timeout, start) +} + +// RandomPort returns a random non-priviledged port. +func RandomPort() int { + min := 1025 + max := 65534 + return min + rand.Intn(max-min) +} + +// GenerateContainerID generated a random container id. +func GenerateContainerID() string { + return ContainerPrefix + uuid.New() +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} \ No newline at end of file diff --git a/vendor/github.com/ory-am/dockertest/elasticsearch.go b/vendor/github.com/ory-am/dockertest/elasticsearch.go new file mode 100644 index 000000000..25f7e88c0 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/elasticsearch.go @@ -0,0 +1,42 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupElasticSearchContainer sets up a real ElasticSearch instance for testing purposes +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupElasticSearchContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 9200) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(ElasticSearchImageName, port, 15*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, ElasticSearchImageName) + }) + return +} + +// ConnectToElasticSearch starts an ElasticSearch image and passes the database url to the connector callback function. +// The url will match the ip:port pattern (e.g. 123.123.123.123:4241) +func ConnectToElasticSearch(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupElasticSearchContainer() + if err != nil { + return c, fmt.Errorf("Could not set up ElasticSearch container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up ElasticSearch container.") +} diff --git a/vendor/github.com/ory-am/dockertest/mockserver.go b/vendor/github.com/ory-am/dockertest/mockserver.go new file mode 100644 index 000000000..f7e6d7729 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/mockserver.go @@ -0,0 +1,66 @@ +package dockertest + +import ( + "fmt" + "time" + "log" + "github.com/go-errors/errors" +) + +// SetupMockserverContainer sets up a real Mockserver instance for testing purposes +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupMockserverContainer() (c ContainerID, ip string, mockPort, proxyPort int, err error) { + mockPort = RandomPort() + proxyPort = RandomPort() + + mockForward := fmt.Sprintf("%d:%d", mockPort, 1080) + proxyForward := fmt.Sprintf("%d:%d", proxyPort, 1090) + + if BindDockerToLocalhost != "" { + mockForward = "127.0.0.1:" + mockForward + proxyForward = "127.0.0.1:" + proxyForward + } + + c, ip, err = SetupMultiportContainer(RabbitMQImageName, []int{ mockPort, proxyPort}, 10*time.Second, func() (string, error) { + res, err := run("--name", GenerateContainerID(), "-d", "-P", "-p", mockForward, "-p", proxyForward, MockserverImageName) + return res, err + }) + return +} + +// ConnectToMockserver starts a Mockserver image and passes the mock and proxy urls to the connector callback functions. +// The urls will match the http://ip:port pattern (e.g. http://123.123.123.123:4241) +func ConnectToMockserver(tries int, delay time.Duration, mockConnector func(url string) bool, proxyConnector func(url string) bool) (c ContainerID, err error) { + c, ip, mockPort, proxyPort, err := SetupMockserverContainer() + if err != nil { + return c, fmt.Errorf("Could not set up Mockserver container: %v", err) + } + + var mockOk, proxyOk bool + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + + if !mockOk { + if mockConnector(fmt.Sprintf("http://%s:%d", ip, mockPort)) { + mockOk = true + } else { + log.Printf("Try %d failed for mock. Retrying.", try) + } + } + if !proxyOk { + if proxyConnector(fmt.Sprintf("http://%s:%d", ip, proxyPort)) { + proxyOk = true + } else { + log.Printf("Try %d failed for proxy. Retrying.", try) + } + } + } + + if mockOk && proxyOk { + return c, nil + } else { + return c, errors.New("Could not set up Mockserver container.") + } +} \ No newline at end of file diff --git a/vendor/github.com/ory-am/dockertest/mongodb.go b/vendor/github.com/ory-am/dockertest/mongodb.go new file mode 100644 index 000000000..72ffbfeeb --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/mongodb.go @@ -0,0 +1,43 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupMongoContainer sets up a real MongoDB instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupMongoContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 27017) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(MongoDBImageName, port, 10*time.Second, func() (string, error) { + res, err := run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, MongoDBImageName) + return res, err + }) + return +} + +// ConnectToMongoDB starts a MongoDB image and passes the database url to the connector callback. +// The url will match the ip:port pattern (e.g. 123.123.123.123:4241) +func ConnectToMongoDB(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupMongoContainer() + if err != nil { + return c, fmt.Errorf("Could not set up MongoDB container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up MongoDB container.") +} diff --git a/vendor/github.com/ory-am/dockertest/mysql.go b/vendor/github.com/ory-am/dockertest/mysql.go new file mode 100644 index 000000000..b521242ca --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/mysql.go @@ -0,0 +1,75 @@ +package dockertest + +import ( + "database/sql" + "errors" + "fmt" + "log" + "time" + + mysql "github.com/go-sql-driver/mysql" +) + +const ( + defaultMySQLDBName = "mysql" +) + +// SetupMySQLContainer sets up a real MySQL instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupMySQLContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 3306) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(MySQLImageName, port, 10*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-p", forward, "-e", fmt.Sprintf("MYSQL_ROOT_PASSWORD=%s", MySQLPassword), MySQLImageName) + }) + return +} + +// ConnectToMySQL starts a MySQL image and passes the database url to the connector callback function. +// The url will match the username:password@tcp(ip:port) pattern (e.g. `root:root@tcp(123.123.123.123:3131)`) +func ConnectToMySQL(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupMySQLContainer() + if err != nil { + return c, fmt.Errorf("Could not set up MySQL container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%s@tcp(%s:%d)/mysql", MySQLUsername, MySQLPassword, ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up MySQL container.") +} + +// SetUpMySQLDatabase connects mysql container with given $connectURL and also creates a new database named $databaseName +// A modified url used to connect the created database will be returned +func SetUpMySQLDatabase(databaseName, connectURL string) (url string, err error) { + if databaseName == defaultMySQLDBName { + return connectURL, nil + } + + db, err := sql.Open("mysql", connectURL) + if err != nil { + return "", err + } + defer db.Close() + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", databaseName)) + if err != nil { + return "", err + } + + // parse dsn + config, err := mysql.ParseDSN(connectURL) + if err != nil { + return "", err + } + config.DBName = databaseName // overwrite database name + return config.FormatDSN(), nil +} diff --git a/vendor/github.com/ory-am/dockertest/nsq.go b/vendor/github.com/ory-am/dockertest/nsq.go new file mode 100644 index 000000000..ce3fa7b24 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/nsq.go @@ -0,0 +1,90 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupNSQdContainer sets up a real NSQ instance for testing purposes +// using a Docker container and executing `/nsqd`. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupNSQdContainer() (c ContainerID, ip string, tcpPort int, httpPort int, err error) { + // --name nsqd -p 4150:4150 -p 4151:4151 nsqio/nsq /nsqd --broadcast-address=192.168.99.100 --lookupd-tcp-address=192.168.99.100:4160 + tcpPort = RandomPort() + httpPort = RandomPort() + tcpForward := fmt.Sprintf("%d:%d", tcpPort, 4150) + if BindDockerToLocalhost != "" { + tcpForward = "127.0.0.1:" + tcpForward + } + + httpForward := fmt.Sprintf("%d:%d", httpPort, 4151) + if BindDockerToLocalhost != "" { + httpForward = "127.0.0.1:" + httpForward + } + + c, ip, err = SetupContainer(NSQImageName, tcpPort, 15*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-P", "-p", tcpForward, "-p", httpForward, NSQImageName, "/nsqd", fmt.Sprintf("--broadcast-address=%s", ip), fmt.Sprintf("--lookupd-tcp-address=%s:4160", ip)) + }) + return +} + +// SetupNSQLookupdContainer sets up a real NSQ instance for testing purposes +// using a Docker container and executing `/nsqlookupd`. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupNSQLookupdContainer() (c ContainerID, ip string, tcpPort int, httpPort int, err error) { + // docker run --name lookupd -p 4160:4160 -p 4161:4161 nsqio/nsq /nsqlookupd + tcpPort = RandomPort() + httpPort = RandomPort() + tcpForward := fmt.Sprintf("%d:%d", tcpPort, 4160) + if BindDockerToLocalhost != "" { + tcpForward = "127.0.0.1:" + tcpForward + } + + httpForward := fmt.Sprintf("%d:%d", httpPort, 4161) + if BindDockerToLocalhost != "" { + httpForward = "127.0.0.1:" + httpForward + } + + c, ip, err = SetupContainer(NSQImageName, tcpPort, 15*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-P", "-p", tcpForward, "-p", httpForward, NSQImageName, "/nsqlookupd") + }) + return +} + +// ConnectToNSQLookupd starts a NSQ image with `/nsqlookupd` running and passes the IP, HTTP port, and TCP port to the connector callback function. +// The url will match the ip pattern (e.g. 123.123.123.123). +func ConnectToNSQLookupd(tries int, delay time.Duration, connector func(ip string, httpPort int, tcpPort int) bool) (c ContainerID, err error) { + c, ip, tcpPort, httpPort, err := SetupNSQLookupdContainer() + if err != nil { + return c, fmt.Errorf("Could not set up NSQLookupd container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + if connector(ip, httpPort, tcpPort) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up NSQLookupd container.") +} + +// ConnectToNSQd starts a NSQ image with `/nsqd` running and passes the IP, HTTP port, and TCP port to the connector callback function. +// The url will match the ip pattern (e.g. 123.123.123.123). +func ConnectToNSQd(tries int, delay time.Duration, connector func(ip string, httpPort int, tcpPort int) bool) (c ContainerID, err error) { + c, ip, tcpPort, httpPort, err := SetupNSQdContainer() + if err != nil { + return c, fmt.Errorf("Could not set up NSQd container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + if connector(ip, httpPort, tcpPort) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up NSQd container.") +} diff --git a/vendor/github.com/ory-am/dockertest/postgres.go b/vendor/github.com/ory-am/dockertest/postgres.go new file mode 100644 index 000000000..22546c290 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/postgres.go @@ -0,0 +1,80 @@ +package dockertest + +import ( + "database/sql" + "errors" + "fmt" + "log" + "net/url" + "time" + + _ "github.com/lib/pq" +) + +// SetupPostgreSQLContainer sets up a real PostgreSQL instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupPostgreSQLContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 5432) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(PostgresImageName, port, 15*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-p", forward, "-e", fmt.Sprintf("POSTGRES_PASSWORD=%s", PostgresPassword), PostgresImageName) + }) + return +} + +// ConnectToPostgreSQL starts a PostgreSQL image and passes the database url to the connector callback. +func ConnectToPostgreSQL(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupPostgreSQLContainer() + if err != nil { + return c, fmt.Errorf("Could not set up PostgreSQL container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("postgres://%s:%s@%s:%d/postgres?sslmode=disable", PostgresUsername, PostgresPassword, ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up PostgreSQL container.") +} + +// SetUpPostgreDatabase connects postgre container with given $connectURL and also creates a new database named $databaseName +// A modified url used to connect the created database will be returned +func SetUpPostgreDatabase(databaseName, connectURL string) (modifiedURL string, err error) { + db, err := sql.Open("postgres", connectURL) + if err != nil { + return "", err + } + defer db.Close() + + count := 0 + err = db.QueryRow( + fmt.Sprintf("SELECT COUNT(*) FROM pg_catalog.pg_database WHERE datname = '%s' ;", databaseName)). + Scan(&count) + if err != nil { + return "", err + } + if count == 0 { + // not found for $databaseName, create it + _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", databaseName)) + if err != nil { + return "", err + } + } + + // replace dbname in url + // from: postgres://postgres:docker@192.168.99.100:9071/postgres?sslmode=disable + // to: postgres://postgres:docker@192.168.99.100:9071/$databaseName?sslmode=disable + u, err := url.Parse(connectURL) + if err != nil { + return "", err + } + u.Path = fmt.Sprintf("/%s", databaseName) + return u.String(), nil +} diff --git a/vendor/github.com/ory-am/dockertest/rabbitmq.go b/vendor/github.com/ory-am/dockertest/rabbitmq.go new file mode 100644 index 000000000..fcc7e8d13 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/rabbitmq.go @@ -0,0 +1,43 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupRabbitMQContainer sets up a real RabbitMQ instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupRabbitMQContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 5672) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(RabbitMQImageName, port, 10*time.Second, func() (string, error) { + res, err := run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, RabbitMQImageName) + return res, err + }) + return +} + +// ConnectToRabbitMQ starts a RabbitMQ image and passes the amqp url to the connector callback. +// The url will match the ip:port pattern (e.g. 123.123.123.123:4241) +func ConnectToRabbitMQ(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupRabbitMQContainer() + if err != nil { + return c, fmt.Errorf("Could not set up RabbitMQ container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up RabbitMQ container.") +} diff --git a/vendor/github.com/ory-am/dockertest/redis.go b/vendor/github.com/ory-am/dockertest/redis.go new file mode 100644 index 000000000..890127586 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/redis.go @@ -0,0 +1,42 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupRedisContainer sets up a real Redis instance for testing purposes +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupRedisContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 6379) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(RedisImageName, port, 15*time.Second, func() (string, error) { + return run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, RedisImageName) + }) + return +} + +// ConnectToRedis starts a Redis image and passes the database url to the connector callback function. +// The url will match the ip:port pattern (e.g. 123.123.123.123:6379) +func ConnectToRedis(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupRedisContainer() + if err != nil { + return c, fmt.Errorf("Could not set up Redis container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up Redis container.") +} diff --git a/vendor/github.com/ory-am/dockertest/rethinkdb.go b/vendor/github.com/ory-am/dockertest/rethinkdb.go new file mode 100644 index 000000000..94d7d35d0 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/rethinkdb.go @@ -0,0 +1,43 @@ +package dockertest + +import ( + "errors" + "fmt" + "log" + "time" +) + +// SetupRethinkDBContainer sets up a real RethinkDB instance for testing purposes, +// using a Docker container. It returns the container ID and its IP address, +// or makes the test fail on error. +func SetupRethinkDBContainer() (c ContainerID, ip string, port int, err error) { + port = RandomPort() + forward := fmt.Sprintf("%d:%d", port, 28015) + if BindDockerToLocalhost != "" { + forward = "127.0.0.1:" + forward + } + c, ip, err = SetupContainer(RethinkDBImageName, port, 10*time.Second, func() (string, error) { + res, err := run("--name", GenerateContainerID(), "-d", "-P", "-p", forward, RethinkDBImageName) + return res, err + }) + return +} + +// ConnectToRethinkDB starts a RethinkDB image and passes the database url to the connector callback. +// The url will match the ip:port pattern (e.g. 123.123.123.123:4241) +func ConnectToRethinkDB(tries int, delay time.Duration, connector func(url string) bool) (c ContainerID, err error) { + c, ip, port, err := SetupRethinkDBContainer() + if err != nil { + return c, fmt.Errorf("Could not set up RethinkDB container: %v", err) + } + + for try := 0; try <= tries; try++ { + time.Sleep(delay) + url := fmt.Sprintf("%s:%d", ip, port) + if connector(url) { + return c, nil + } + log.Printf("Try %d failed. Retrying.", try) + } + return c, errors.New("Could not set up RethinkDB container.") +} diff --git a/vendor/github.com/ory-am/dockertest/vars.go b/vendor/github.com/ory-am/dockertest/vars.go new file mode 100644 index 000000000..b076e8e23 --- /dev/null +++ b/vendor/github.com/ory-am/dockertest/vars.go @@ -0,0 +1,72 @@ +package dockertest + +import "github.com/ory-am/common/env" + +// Dockertest configuration +var ( + // Debug if set, prevents any container from being removed. + Debug bool + + // DockerMachineAvailable if true, uses docker-machine to run docker commands (for running tests on Windows and Mac OS) + DockerMachineAvailable bool + + // DockerMachineName is the machine's name. You might want to use a dedicated machine for running your tests. + // You can set this variable either directly or by defining a DOCKERTEST_IMAGE_NAME env variable. + DockerMachineName = env.Getenv("DOCKERTEST_IMAGE_NAME", "default") + + // BindDockerToLocalhost if set, forces docker to bind the image to localhost. This for example is required when running tests on travis-ci. + // You can set this variable either directly or by defining a DOCKERTEST_BIND_LOCALHOST env variable. + // FIXME DOCKER_BIND_LOCALHOST remove legacy support + BindDockerToLocalhost = env.Getenv("DOCKERTEST_BIND_LOCALHOST", env.Getenv("DOCKER_BIND_LOCALHOST", "")) + + // ContainerPrefix will be prepended to all containers started by dockertest to make identification of these "test images" hassle-free. + ContainerPrefix = env.Getenv("DOCKERTEST_CONTAINER_PREFIX", "dockertest-") +) + +// Image configuration +var ( + // MongoDBImageName is the MongoDB image name on dockerhub. + MongoDBImageName = env.Getenv("DOCKERTEST_MONGODB_IMAGE_NAME", "mongo") + + // MySQLImageName is the MySQL image name on dockerhub. + MySQLImageName = env.Getenv("DOCKERTEST_MYSQL_IMAGE_NAME", "mysql") + + // PostgresImageName is the PostgreSQL image name on dockerhub. + PostgresImageName = env.Getenv("DOCKERTEST_POSTGRES_IMAGE_NAME", "postgres") + + // ElasticSearchImageName is the ElasticSearch image name on dockerhub. + ElasticSearchImageName = env.Getenv("DOCKERTEST_ELASTICSEARCH_IMAGE_NAME", "elasticsearch") + + // RedisImageName is the Redis image name on dockerhub. + RedisImageName = env.Getenv("DOCKERTEST_REDIS_IMAGE_NAME", "redis") + + // NSQImageName is the NSQ image name on dockerhub. + NSQImageName = env.Getenv("DOCKERTEST_NSQ_IMAGE_NAME", "nsqio/nsq") + + // RethinkDBImageName is the RethinkDB image name on dockerhub. + RethinkDBImageName = env.Getenv("DOCKERTEST_RETHINKDB_IMAGE_NAME", "rethinkdb") + + // RabbitMQImage name is the RabbitMQ image name on dockerhub. + RabbitMQImageName = env.Getenv("DOCKERTEST_RABBITMQ_IMAGE_NAME", "rabbitmq") + + // ActiveMQImage name is the ActiveMQ image name on dockerhub. + ActiveMQImageName = env.Getenv("DOCKERTEST_ACTIVEMQ_IMAGE_NAME", "webcenter/activemq") + + // MockserverImageName name is the Mockserver image name on dockerhub. + MockserverImageName = env.Getenv("DOCKERTEST_MOCKSERVER_IMAGE_NAME", "jamesdbloom/mockserver") +) + +// Username and password configuration +var ( + // MySQLUsername must be passed as username when connecting to mysql + MySQLUsername = "root" + + // MySQLPassword must be passed as password when connecting to mysql + MySQLPassword = "root" + + // PostgresUsername must be passed as username when connecting to postgres + PostgresUsername = "postgres" + + // PostgresPassword must be passed as password when connecting to postgres + PostgresPassword = "docker" +) diff --git a/vendor/vendor.json b/vendor/vendor.json index 45efd218f..dab0a68b6 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -18,6 +18,12 @@ "path": "appengine_internal/base", "revision": "" }, + { + "checksumSHA1": "ZjT4/uxN8N5WXikTtY1LdmMtL9Q=", + "path": "camlistore.org/pkg/netutil", + "revision": "c332b2881d09003e01491f2658ccf1dbbb006169", + "revisionTime": "2016-06-29T15:04:51Z" + }, { "path": "context", "revision": "" @@ -628,6 +634,12 @@ "revision": "930cc805232909c38f2e68310b1e21f71b056d59", "revisionTime": "2016-01-09T14:21:19Z" }, + { + "checksumSHA1": "CXJIw4GNmwqc+c4ZCeVj2mMm1Uw=", + "path": "github.com/ory-am/dockertest", + "revision": "293f0a0aac817a67dcf656af315a43f2db140c2a", + "revisionTime": "2016-06-21T08:39:56Z" + }, { "checksumSHA1": "xN14ZoFcgefABp24aowYtQVMDsc=", "path": "github.com/pborman/uuid",