diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8b15ffb69..5a35ef3c9 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -81,11 +81,11 @@ }, { "ImportPath": "github.com/garyburd/redigo/internal", - "Rev": "2668ae2944701f73206a228638407926dea859b1" + "Rev": "836b6e58b3358112c8291565d01c35b8764070d7" }, { "ImportPath": "github.com/garyburd/redigo/redis", - "Rev": "2668ae2944701f73206a228638407926dea859b1" + "Rev": "836b6e58b3358112c8291565d01c35b8764070d7" }, { "ImportPath": "github.com/go-ini/ini", diff --git a/backends/client.go b/backends/client.go index 19e76e3fd..5e60228a3 100644 --- a/backends/client.go +++ b/backends/client.go @@ -44,7 +44,7 @@ func New(config Config) (StoreClient, error) { case "rancher": return rancher.NewRancherClient(backendNodes) case "redis": - return redis.NewRedisClient(backendNodes) + return redis.NewRedisClient(backendNodes, config.ClientKey) case "env": return env.NewEnvClient() case "vault": diff --git a/backends/redis/client.go b/backends/redis/client.go index 19984da71..38d3aacb4 100644 --- a/backends/redis/client.go +++ b/backends/redis/client.go @@ -18,7 +18,7 @@ type Client struct { // Iterate through `machines`, trying to connect to each in turn. // Returns the first successful connection or the last error encountered. // Assumes that `machines` is non-empty. -func tryConnect(machines []string) (redis.Conn, error) { +func tryConnect(machines []string, options ...string) (redis.Conn, error) { var err error for _, address := range machines { var conn redis.Conn @@ -27,7 +27,19 @@ func tryConnect(machines []string) (redis.Conn, error) { network = "unix" } log.Debug(fmt.Sprintf("Trying to connect to redis node %s", address)) - conn, err = redis.DialTimeout(network, address, time.Second, time.Second, time.Second) + + dialops := []redis.DialOption{ + redis.DialConnectTimeout(time.Second), + redis.DialReadTimeout(time.Second), + redis.DialWriteTimeout(time.Second), + } + + if len(options) > 0 { + dialops = append(dialops, redis.DialPassword(options[0])) + } + + conn, err = redis.Dial(network, address, dialops...) + if err != nil { continue } @@ -54,7 +66,7 @@ func (c *Client) connectedClient() (redis.Conn, error) { // Existing client could have been deleted by previous block if c.client == nil { var err error - c.client, err = tryConnect(c.machines) + c.client, err = tryConnect(c.machines, c.options) if err != nil { return nil, err } @@ -65,10 +77,10 @@ func (c *Client) connectedClient() (redis.Conn, error) { // NewRedisClient returns an *redis.Client with a connection to named machines. // It returns an error if a connection to the cluster cannot be made. -func NewRedisClient(machines []string) (*Client, error) { +func NewRedisClient(machines []string, options ...string) (*Client, error) { var err error - clientWrapper := &Client{ machines : machines, client: nil } - clientWrapper.client, err = tryConnect(machines) + clientWrapper := &Client{ machines : machines, options : options, client: nil } + clientWrapper.client, err = tryConnect(machines, options) return clientWrapper, err } diff --git a/vendor/github.com/garyburd/redigo/LICENSE b/vendor/github.com/garyburd/redigo/LICENSE new file mode 100644 index 000000000..67db85882 --- /dev/null +++ b/vendor/github.com/garyburd/redigo/LICENSE @@ -0,0 +1,175 @@ + + 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. diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo.go b/vendor/github.com/garyburd/redigo/internal/commandinfo.go index ce78eff6f..dbc60fc8e 100644 --- a/vendor/github.com/garyburd/redigo/internal/commandinfo.go +++ b/vendor/github.com/garyburd/redigo/internal/commandinfo.go @@ -40,6 +40,15 @@ var commandInfos = map[string]CommandInfo{ "MONITOR": {Set: MonitorState}, } +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + func LookupCommandInfo(commandName string) CommandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } return commandInfos[strings.ToUpper(commandName)] } diff --git a/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go b/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go deleted file mode 100644 index 5f955c424..000000000 --- a/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2014 Gary Burd -// -// 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 redistest contains utilities for writing Redigo tests. -package redistest - -import ( - "errors" - "time" - - "github.com/garyburd/redigo/redis" -) - -type testConn struct { - redis.Conn -} - -func (t testConn) Close() error { - _, err := t.Conn.Do("SELECT", "9") - if err != nil { - return nil - } - _, err = t.Conn.Do("FLUSHDB") - if err != nil { - return err - } - return t.Conn.Close() -} - -// Dial dials the local Redis server and selects database 9. To prevent -// stomping on real data, DialTestDB fails if database 9 contains data. The -// returned connection flushes database 9 on close. -func Dial() (redis.Conn, error) { - c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) - if err != nil { - return nil, err - } - - _, err = c.Do("SELECT", "9") - if err != nil { - return nil, err - } - - n, err := redis.Int(c.Do("DBSIZE")) - if err != nil { - return nil, err - } - - if n != 0 { - return nil, errors.New("database #9 is not empty, test can not continue") - } - - return testConn{c}, nil -} diff --git a/vendor/github.com/garyburd/redigo/redis/conn.go b/vendor/github.com/garyburd/redigo/redis/conn.go index ac0e971c4..6a3819f1d 100644 --- a/vendor/github.com/garyburd/redigo/redis/conn.go +++ b/vendor/github.com/garyburd/redigo/redis/conn.go @@ -21,6 +21,8 @@ import ( "fmt" "io" "net" + "net/url" + "regexp" "strconv" "sync" "time" @@ -51,56 +53,164 @@ type conn struct { numScratch [40]byte } -// Dial connects to the Redis server at the given network and address. -func Dial(network, address string) (Conn, error) { - dialer := xDialer{} - return dialer.Dial(network, address) -} - // DialTimeout acts like Dial but takes timeouts for establishing the // connection to the server, writing a command and reading a reply. +// +// Deprecated: Use Dial with options instead. func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { - netDialer := net.Dialer{Timeout: connectTimeout} - dialer := xDialer{ - NetDial: netDialer.Dial, - ReadTimeout: readTimeout, - WriteTimeout: writeTimeout, - } - return dialer.Dial(network, address) + return Dial(network, address, + DialConnectTimeout(connectTimeout), + DialReadTimeout(readTimeout), + DialWriteTimeout(writeTimeout)) +} + +// DialOption specifies an option for dialing a Redis server. +type DialOption struct { + f func(*dialOptions) +} + +type dialOptions struct { + readTimeout time.Duration + writeTimeout time.Duration + dial func(network, addr string) (net.Conn, error) + db int + password string +} + +// DialReadTimeout specifies the timeout for reading a single command reply. +func DialReadTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.readTimeout = d + }} +} + +// DialWriteTimeout specifies the timeout for writing a single command. +func DialWriteTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.writeTimeout = d + }} } -// A Dialer specifies options for connecting to a Redis server. -type xDialer struct { - // NetDial specifies the dial function for creating TCP connections. If - // NetDial is nil, then net.Dial is used. - NetDial func(network, addr string) (net.Conn, error) +// DialConnectTimeout specifies the timeout for connecting to the Redis server. +func DialConnectTimeout(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + dialer := net.Dialer{Timeout: d} + do.dial = dialer.Dial + }} +} + +// DialNetDial specifies a custom dial function for creating TCP +// connections. If this option is left out, then net.Dial is +// used. DialNetDial overrides DialConnectTimeout. +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { + return DialOption{func(do *dialOptions) { + do.dial = dial + }} +} - // ReadTimeout specifies the timeout for reading a single command - // reply. If ReadTimeout is zero, then no timeout is used. - ReadTimeout time.Duration +// DialDatabase specifies the database to select when dialing a connection. +func DialDatabase(db int) DialOption { + return DialOption{func(do *dialOptions) { + do.db = db + }} +} - // WriteTimeout specifies the timeout for writing a single command. If - // WriteTimeout is zero, then no timeout is used. - WriteTimeout time.Duration +// DialPassword specifies the password to use when connecting to +// the Redis server. +func DialPassword(password string) DialOption { + return DialOption{func(do *dialOptions) { + do.password = password + }} } -// Dial connects to the Redis server at address on the named network. -func (d *xDialer) Dial(network, address string) (Conn, error) { - dial := d.NetDial - if dial == nil { - dial = net.Dial +// Dial connects to the Redis server at the given network and +// address using the specified options. +func Dial(network, address string, options ...DialOption) (Conn, error) { + do := dialOptions{ + dial: net.Dial, } - netConn, err := dial(network, address) + for _, option := range options { + option.f(&do) + } + + netConn, err := do.dial(network, address) if err != nil { return nil, err } - return &conn{ + c := &conn{ conn: netConn, bw: bufio.NewWriter(netConn), br: bufio.NewReader(netConn), - readTimeout: d.ReadTimeout, - writeTimeout: d.WriteTimeout, - }, nil + readTimeout: do.readTimeout, + writeTimeout: do.writeTimeout, + } + + if do.password != "" { + if _, err := c.Do("AUTH", do.password); err != nil { + netConn.Close() + return nil, err + } + } + + if do.db != 0 { + if _, err := c.Do("SELECT", do.db); err != nil { + netConn.Close() + return nil, err + } + } + + return c, nil +} + +var pathDBRegexp = regexp.MustCompile(`/(\d+)\z`) + +// DialURL connects to a Redis server at the given URL using the Redis +// URI scheme. URLs should follow the draft IANA specification for the +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + u, err := url.Parse(rawurl) + if err != nil { + return nil, err + } + + if u.Scheme != "redis" { + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) + } + + // As per the IANA draft spec, the host defaults to localhost and + // the port defaults to 6379. + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // assume port is missing + host = u.Host + port = "6379" + } + if host == "" { + host = "localhost" + } + address := net.JoinHostPort(host, port) + + if u.User != nil { + password, isSet := u.User.Password() + if isSet { + options = append(options, DialPassword(password)) + } + } + + match := pathDBRegexp.FindStringSubmatch(u.Path) + if len(match) == 2 { + db, err := strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + if db != 0 { + options = append(options, DialDatabase(db)) + } + } else if u.Path != "" { + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) + } + + return Dial("tcp", address, options...) } // NewConn returns a new Redigo connection for the given net connection. @@ -417,7 +527,9 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { } if cmd != "" { - c.writeCommand(cmd, args) + if err := c.writeCommand(cmd, args); err != nil { + return nil, c.fatal(err) + } } if err := c.bw.Flush(); err != nil { diff --git a/vendor/github.com/garyburd/redigo/redis/pool.go b/vendor/github.com/garyburd/redigo/redis/pool.go index 9daf2e33f..d66ef84b6 100644 --- a/vendor/github.com/garyburd/redigo/redis/pool.go +++ b/vendor/github.com/garyburd/redigo/redis/pool.go @@ -94,7 +94,10 @@ var ( type Pool struct { // Dial is an application supplied function for creating and configuring a - // connection + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). Dial func() (Conn, error) // TestOnBorrow is an optional application supplied function for checking @@ -116,7 +119,7 @@ type Pool struct { // the timeout to a value less than the server's timeout. IdleTimeout time.Duration - // If Wait is true and the pool is at the MaxIdle limit, then Get() waits + // If Wait is true and the pool is at the MaxActive limit, then Get() waits // for a connection to be returned to the pool before returning. Wait bool @@ -135,8 +138,9 @@ type idleConn struct { t time.Time } -// NewPool creates a new pool. This function is deprecated. Applications should -// initialize the Pool fields directly as shown in example. +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { return &Pool{Dial: newFn, MaxIdle: maxIdle} } diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub.go b/vendor/github.com/garyburd/redigo/redis/pubsub.go index f0790429f..c0ecce824 100644 --- a/vendor/github.com/garyburd/redigo/redis/pubsub.go +++ b/vendor/github.com/garyburd/redigo/redis/pubsub.go @@ -14,9 +14,7 @@ package redis -import ( - "errors" -) +import "errors" // Subscription represents a subscribe or unsubscribe notification. type Subscription struct { @@ -54,6 +52,11 @@ type PMessage struct { Data []byte } +// Pong represents a pubsub pong notification. +type Pong struct { + Data string +} + // PubSubConn wraps a Conn with convenience methods for subscribers. type PubSubConn struct { Conn Conn @@ -90,9 +93,15 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { return c.Conn.Flush() } -// Receive returns a pushed message as a Subscription, Message, PMessage or -// error. The return value is intended to be used directly in a type switch as -// illustrated in the PubSubConn example. +// Ping sends a PING to the server with the specified data. +func (c PubSubConn) Ping(data string) error { + c.Conn.Send("PING", data) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, PMessage, Pong +// or error. The return value is intended to be used directly in a type switch +// as illustrated in the PubSubConn example. func (c PubSubConn) Receive() interface{} { reply, err := Values(c.Conn.Receive()) if err != nil { @@ -124,6 +133,12 @@ func (c PubSubConn) Receive() interface{} { return err } return s + case "pong": + var p Pong + if _, err := Scan(reply, &p.Data); err != nil { + return err + } + return p } return errors.New("redigo: unknown pubsub notification") } diff --git a/vendor/github.com/garyburd/redigo/redis/reply.go b/vendor/github.com/garyburd/redigo/redis/reply.go index 5648f930d..57896147f 100644 --- a/vendor/github.com/garyburd/redigo/redis/reply.go +++ b/vendor/github.com/garyburd/redigo/redis/reply.go @@ -215,7 +215,9 @@ func Bool(reply interface{}, err error) (bool, error) { return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) } -// MultiBulk is deprecated. Use Values. +// MultiBulk is a helper that converts an array command reply to a []interface{}. +// +// Deprecated: Use Values instead. func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } // Values is a helper that converts an array command reply to a []interface{}. @@ -271,13 +273,40 @@ func Strings(reply interface{}, err error) ([]string, error) { return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) } +// ByteSlices is a helper that converts an array command reply to a [][]byte. +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array +// items are stay nil. ByteSlices returns an error if an array item is not a +// bulk string or nil. +func ByteSlices(reply interface{}, err error) ([][]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([][]byte, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + p, ok := reply[i].([]byte) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) + } + result[i] = p + } + return result, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) +} + // Ints is a helper that converts an array command reply to a []int. If // err is not equal to nil, then Ints returns nil, err. func Ints(reply interface{}, err error) ([]int, error) { var ints []int - if reply == nil { - return ints, ErrNil - } values, err := Values(reply, err) if err != nil { return ints, err @@ -310,3 +339,55 @@ func StringMap(result interface{}, err error) (map[string]string, error) { } return m, nil } + +// IntMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]int. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func IntMap(result interface{}, err error) (map[string]int, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: IntMap expects even number of values result") + } + m := make(map[string]int, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: ScanMap key not a bulk string value") + } + value, err := Int(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} + +// Int64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]int64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Int64Map(result interface{}, err error) (map[string]int64, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: Int64Map expects even number of values result") + } + m := make(map[string]int64, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].([]byte) + if !ok { + return nil, errors.New("redigo: ScanMap key not a bulk string value") + } + value, err := Int64(values[i+1], nil) + if err != nil { + return nil, err + } + m[string(key)] = value + } + return m, nil +} diff --git a/vendor/github.com/garyburd/redigo/redis/scan.go b/vendor/github.com/garyburd/redigo/redis/scan.go index 8c9cfa18d..962e94bcc 100644 --- a/vendor/github.com/garyburd/redigo/redis/scan.go +++ b/vendor/github.com/garyburd/redigo/redis/scan.go @@ -32,11 +32,25 @@ func ensureLen(d reflect.Value, n int) { } func cannotConvert(d reflect.Value, s interface{}) error { - return fmt.Errorf("redigo: Scan cannot convert from %s to %s", - reflect.TypeOf(s), d.Type()) + var sname string + switch s.(type) { + case string: + sname = "Redis simple string" + case Error: + sname = "Redis error" + case int64: + sname = "Redis integer" + case []byte: + sname = "Redis bulk string" + case []interface{}: + sname = "Redis array" + default: + sname = reflect.TypeOf(s).String() + } + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) } -func convertAssignBytes(d reflect.Value, s []byte) (err error) { +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { switch d.Type().Kind() { case reflect.Float32, reflect.Float64: var x float64 @@ -98,7 +112,7 @@ func convertAssignInt(d reflect.Value, s int64) (err error) { func convertAssignValue(d reflect.Value, s interface{}) (err error) { switch s := s.(type) { case []byte: - err = convertAssignBytes(d, s) + err = convertAssignBulkString(d, s) case int64: err = convertAssignInt(d, s) default: @@ -107,7 +121,7 @@ func convertAssignValue(d reflect.Value, s interface{}) (err error) { return err } -func convertAssignValues(d reflect.Value, s []interface{}) error { +func convertAssignArray(d reflect.Value, s []interface{}) error { if d.Type().Kind() != reflect.Slice { return cannotConvert(d, s) } @@ -144,7 +158,7 @@ func convertAssign(d interface{}, s interface{}) (err error) { if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { - err = convertAssignBytes(d.Elem(), s) + err = convertAssignBulkString(d.Elem(), s) } } case int64: @@ -169,6 +183,13 @@ func convertAssign(d interface{}, s interface{}) (err error) { err = convertAssignInt(d.Elem(), s) } } + case string: + switch d := d.(type) { + case *string: + *d = string(s) + default: + err = cannotConvert(reflect.ValueOf(d), s) + } case []interface{}: switch d := d.(type) { case *[]interface{}: @@ -181,7 +202,7 @@ func convertAssign(d interface{}, s interface{}) (err error) { if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { err = cannotConvert(d, s) } else { - err = convertAssignValues(d.Elem(), s) + err = convertAssignArray(d.Elem(), s) } } case Error: @@ -206,12 +227,13 @@ func convertAssign(d interface{}, s interface{}) (err error) { // following the copied values. func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { if len(src) < len(dest) { - return nil, errors.New("redigo: Scan array short") + return nil, errors.New("redigo.Scan: array short") } var err error for i, d := range dest { err = convertAssign(d, src[i]) if err != nil { + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) break } } @@ -219,9 +241,9 @@ func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { } type fieldSpec struct { - name string - index []int - //omitEmpty bool + name string + index []int + omitEmpty bool } type structSpec struct { @@ -237,7 +259,7 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st for i := 0; i < t.NumField(); i++ { f := t.Field(i) switch { - case f.PkgPath != "": + case f.PkgPath != "" && !f.Anonymous: // Ignore unexported fields. case f.Anonymous: // TODO: Handle pointers. Requires change to decoder and @@ -258,10 +280,10 @@ func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *st } for _, s := range p[1:] { switch s { - //case "omitempty": - // fs.omitempty = true + case "omitempty": + fs.omitEmpty = true default: - panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) } } } @@ -321,7 +343,7 @@ func structSpecForType(t reflect.Type) *structSpec { return ss } -var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") // ScanStruct scans alternating names and values from src to a struct. The // HGETALL and CONFIG GET commands return replies in this format. @@ -350,7 +372,7 @@ func ScanStruct(src []interface{}, dest interface{}) error { ss := structSpecForType(d.Type()) if len(src)%2 != 0 { - return errors.New("redigo: ScanStruct expects even number of values in values") + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") } for i := 0; i < len(src); i += 2 { @@ -360,21 +382,21 @@ func ScanStruct(src []interface{}, dest interface{}) error { } name, ok := src[i].([]byte) if !ok { - return errors.New("redigo: ScanStruct key not a bulk string value") + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) } fs := ss.fieldSpec(name) if fs == nil { continue } if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) } } return nil } var ( - errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") ) // ScanSlice scans src to the slice pointed to by dest. The elements the dest @@ -407,7 +429,7 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error continue } if err := convertAssignValue(d.Index(i), s); err != nil { - return err + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) } } return nil @@ -420,18 +442,18 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error for i, name := range fieldNames { fss[i] = ss.m[name] if fss[i] == nil { - return errors.New("redigo: ScanSlice bad field name " + name) + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) } } } if len(fss) == 0 { - return errors.New("redigo: ScanSlice no struct fields") + return errors.New("redigo.ScanSlice: no struct fields") } n := len(src) / len(fss) if n*len(fss) != len(src) { - return errors.New("redigo: ScanSlice length not a multiple of struct field count") + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") } ensureLen(d, n) @@ -449,7 +471,7 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error continue } if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return err + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) } } } @@ -507,6 +529,26 @@ func flattenStruct(args Args, v reflect.Value) Args { ss := structSpecForType(v.Type()) for _, fs := range ss.l { fv := v.FieldByIndex(fs.index) + if fs.omitEmpty { + var empty = false + switch fv.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + empty = fv.Len() == 0 + case reflect.Bool: + empty = !fv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + empty = fv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + empty = fv.Uint() == 0 + case reflect.Float32, reflect.Float64: + empty = fv.Float() == 0 + case reflect.Interface, reflect.Ptr: + empty = fv.IsNil() + } + if empty { + continue + } + } args = append(args, fs.name, fv.Interface()) } return args