github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/api/datastore/redis/redis.go (about) 1 package redis 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/url" 7 "regexp" 8 "strings" 9 "time" 10 11 "context" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/garyburd/redigo/redis" 15 "github.com/iron-io/functions/api/datastore/internal/datastoreutil" 16 "github.com/iron-io/functions/api/models" 17 ) 18 19 type RedisDataStore struct { 20 conn redis.Conn 21 } 22 23 func New(url *url.URL) (models.Datastore, error) { 24 pool := &redis.Pool{ 25 MaxIdle: 4, 26 // I'm not sure if allowing the pool to block if more than 16 connections are required is a good idea. 27 MaxActive: 16, 28 Wait: true, 29 IdleTimeout: 300 * time.Second, 30 Dial: func() (redis.Conn, error) { 31 return redis.DialURL(url.String()) 32 }, 33 TestOnBorrow: func(c redis.Conn, t time.Time) error { 34 _, err := c.Do("PING") 35 return err 36 }, 37 } 38 // Force a connection so we can fail in case of error. 39 conn := pool.Get() 40 41 if err := conn.Err(); err != nil { 42 logrus.WithError(err).Fatal("Error connecting to redis") 43 } 44 ds := &RedisDataStore{ 45 conn: conn, 46 } 47 return datastoreutil.NewValidator(ds), nil 48 } 49 50 func (ds *RedisDataStore) setApp(app *models.App) (*models.App, error) { 51 appBytes, err := json.Marshal(app) 52 if err != nil { 53 return nil, err 54 } 55 56 if _, err := ds.conn.Do("HSET", "apps", app.Name, appBytes); err != nil { 57 return nil, err 58 } 59 return app, nil 60 } 61 62 func (ds *RedisDataStore) InsertApp(ctx context.Context, app *models.App) (*models.App, error) { 63 reply, err := ds.conn.Do("HEXISTS", "apps", app.Name) 64 if err != nil { 65 return nil, err 66 } 67 if exists, err := redis.Bool(reply, err); err != nil { 68 return nil, err 69 } else if exists { 70 return nil, models.ErrAppsAlreadyExists 71 } 72 73 return ds.setApp(app) 74 } 75 76 func (ds *RedisDataStore) UpdateApp(ctx context.Context, newapp *models.App) (*models.App, error) { 77 app, err := ds.GetApp(ctx, newapp.Name) 78 if err != nil { 79 return nil, err 80 } 81 82 app.UpdateConfig(newapp.Config) 83 84 return ds.setApp(app) 85 } 86 87 func (ds *RedisDataStore) RemoveApp(ctx context.Context, appName string) error { 88 if _, err := ds.conn.Do("HDEL", "apps", appName); err != nil { 89 return err 90 } 91 92 return nil 93 } 94 95 func (ds *RedisDataStore) GetApp(ctx context.Context, name string) (*models.App, error) { 96 reply, err := ds.conn.Do("HGET", "apps", name) 97 if err != nil { 98 return nil, err 99 } else if reply == nil { 100 return nil, models.ErrAppsNotFound 101 } 102 103 res := &models.App{} 104 if err := json.Unmarshal(reply.([]byte), res); err != nil { 105 return nil, err 106 } 107 108 return res, nil 109 } 110 111 func (ds *RedisDataStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*models.App, error) { 112 res := []*models.App{} 113 114 reply, err := ds.conn.Do("HGETALL", "apps") 115 if err != nil { 116 return nil, err 117 } 118 119 apps, err := redis.StringMap(reply, err) 120 if err != nil { 121 return nil, err 122 } 123 124 for _, v := range apps { 125 var app models.App 126 if err := json.Unmarshal([]byte(v), &app); err != nil { 127 return nil, err 128 } 129 if applyAppFilter(&app, filter) { 130 res = append(res, &app) 131 } 132 } 133 return res, nil 134 } 135 136 func (ds *RedisDataStore) setRoute(set string, route *models.Route) (*models.Route, error) { 137 buf, err := json.Marshal(route) 138 if err != nil { 139 return nil, err 140 } 141 142 if _, err := ds.conn.Do("HSET", set, route.Path, buf); err != nil { 143 return nil, err 144 } 145 146 return route, nil 147 } 148 149 func (ds *RedisDataStore) InsertRoute(ctx context.Context, route *models.Route) (*models.Route, error) { 150 reply, err := ds.conn.Do("HEXISTS", "apps", route.AppName) 151 if err != nil { 152 return nil, err 153 } 154 if exists, err := redis.Bool(reply, err); err != nil { 155 return nil, err 156 } else if !exists { 157 return nil, models.ErrAppsNotFound 158 } 159 160 hset := fmt.Sprintf("routes:%s", route.AppName) 161 162 reply, err = ds.conn.Do("HEXISTS", hset, route.Path) 163 if err != nil { 164 return nil, err 165 } 166 167 if exists, err := redis.Bool(reply, err); err != nil { 168 return nil, err 169 } else if exists { 170 return nil, models.ErrRoutesAlreadyExists 171 } 172 173 return ds.setRoute(hset, route) 174 } 175 176 func (ds *RedisDataStore) UpdateRoute(ctx context.Context, newroute *models.Route) (*models.Route, error) { 177 route, err := ds.GetRoute(ctx, newroute.AppName, newroute.Path) 178 if err != nil { 179 return nil, err 180 } 181 182 route.Update(newroute) 183 184 hset := fmt.Sprintf("routes:%s", route.AppName) 185 186 return ds.setRoute(hset, route) 187 } 188 189 func (ds *RedisDataStore) RemoveRoute(ctx context.Context, appName, routePath string) error { 190 hset := fmt.Sprintf("routes:%s", appName) 191 if n, err := ds.conn.Do("HDEL", hset, routePath); err != nil { 192 return err 193 } else if n == 0 { 194 return models.ErrRoutesRemoving 195 } 196 197 return nil 198 } 199 200 func (ds *RedisDataStore) GetRoute(ctx context.Context, appName, routePath string) (*models.Route, error) { 201 hset := fmt.Sprintf("routes:%s", appName) 202 reply, err := ds.conn.Do("HGET", hset, routePath) 203 if err != nil { 204 return nil, err 205 } else if reply == nil { 206 return nil, models.ErrRoutesNotFound 207 } 208 209 var route models.Route 210 if err := json.Unmarshal(reply.([]byte), &route); err != nil { 211 return nil, err 212 } 213 214 return &route, nil 215 } 216 217 func (ds *RedisDataStore) GetRoutes(ctx context.Context, filter *models.RouteFilter) ([]*models.Route, error) { 218 res := []*models.Route{} 219 220 reply, err := ds.conn.Do("HKEYS", "apps") 221 if err != nil { 222 return nil, err 223 } else if reply == nil { 224 return nil, models.ErrRoutesNotFound 225 } 226 paths, err := redis.Strings(reply, err) 227 228 for _, path := range paths { 229 hset := fmt.Sprintf("routes:%s", path) 230 reply, err := ds.conn.Do("HGETALL", hset) 231 if err != nil { 232 return nil, err 233 } else if reply == nil { 234 return nil, models.ErrRoutesNotFound 235 } 236 routes, err := redis.StringMap(reply, err) 237 238 for _, v := range routes { 239 var route models.Route 240 if err := json.Unmarshal([]byte(v), &route); err != nil { 241 return nil, err 242 } 243 if applyRouteFilter(&route, filter) { 244 res = append(res, &route) 245 } 246 } 247 } 248 249 return res, nil 250 } 251 252 func (ds *RedisDataStore) GetRoutesByApp(ctx context.Context, appName string, filter *models.RouteFilter) ([]*models.Route, error) { 253 if filter == nil { 254 filter = new(models.RouteFilter) 255 } 256 filter.AppName = appName 257 res := []*models.Route{} 258 259 hset := fmt.Sprintf("routes:%s", appName) 260 reply, err := ds.conn.Do("HGETALL", hset) 261 if err != nil { 262 return nil, err 263 } else if reply == nil { 264 return nil, models.ErrRoutesNotFound 265 } 266 routes, err := redis.StringMap(reply, err) 267 268 for _, v := range routes { 269 var route models.Route 270 if err := json.Unmarshal([]byte(v), &route); err != nil { 271 return nil, err 272 } 273 if applyRouteFilter(&route, filter) { 274 res = append(res, &route) 275 } 276 } 277 278 return res, nil 279 } 280 281 func (ds *RedisDataStore) Put(ctx context.Context, key, value []byte) error { 282 if _, err := ds.conn.Do("HSET", "extras", key, value); err != nil { 283 return err 284 } 285 286 return nil 287 } 288 289 func (ds *RedisDataStore) Get(ctx context.Context, key []byte) ([]byte, error) { 290 value, err := ds.conn.Do("HGET", "extras", key) 291 if err != nil { 292 return nil, err 293 } 294 295 return value.([]byte), nil 296 } 297 298 func applyAppFilter(app *models.App, filter *models.AppFilter) bool { 299 if filter != nil && filter.Name != "" { 300 nameLike, err := regexp.MatchString(strings.Replace(filter.Name, "%", ".*", -1), app.Name) 301 return err == nil && nameLike 302 } 303 304 return true 305 } 306 307 func applyRouteFilter(route *models.Route, filter *models.RouteFilter) bool { 308 return filter == nil || (filter.Path == "" || route.Path == filter.Path) && 309 (filter.AppName == "" || route.AppName == filter.AppName) && 310 (filter.Image == "" || route.Image == filter.Image) 311 }