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  }