Update CI following latest plugindemo version (#34)

* Update CI following latest plugindemo version

* disable some linter

* Disable some linter

* Fix linter in redis, add error logger

* 🚨 Fix lint errors in private packages

* 🚨 Update lints in package and core code

* 🚨 exclude unfixable lint errors

* 🚨 update lint at package level and ignore necessary global variable

* 🍱 fix lint qnd merge

* 🚨 Fix Lint for string

* 🚨 Fix lint for bouncer

* 🚨 Fix error

* Fix weird linter error

Co-authored-by: Max Lerebourg <maxlerebourg@gmail.com>
This commit is contained in:
mathieuHa
2022-11-19 19:35:01 +01:00
committed by GitHub
co-authored by Max Lerebourg
parent 2d0bea8eec
commit 0a54f7b09f
9 changed files with 117 additions and 65 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
strategy: strategy:
matrix: matrix:
go-version: [ 1.17, 1.x ] go-version: [ 1.18, 1.x ]
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
steps: steps:
+3 -3
View File
@@ -12,9 +12,9 @@ jobs:
name: Main Process name: Main Process
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
GO_VERSION: 1.17 GO_VERSION: 1.18
GOLANGCI_LINT_VERSION: v1.46.2 GOLANGCI_LINT_VERSION: v1.50.0
YAEGI_VERSION: v0.13.0 YAEGI_VERSION: v0.14.2
CGO_ENABLED: 0 CGO_ENABLED: 0
defaults: defaults:
run: run:
+14 -2
View File
@@ -29,11 +29,20 @@ linters-settings:
linters: linters:
enable-all: true enable-all: true
disable: disable:
- deadcode # deprecated
- exhaustivestruct # deprecated
- golint # deprecated
- ifshort # deprecated
- interfacer # deprecated - interfacer # deprecated
- maligned # deprecated - maligned # deprecated
- nosnakecase # deprecated
- scopelint # deprecated - scopelint # deprecated
- golint # deprecated - scopelint # deprecated
- exhaustivestruct # deprecated - structcheck # deprecated
- varcheck # deprecated
- sqlclosecheck # not relevant (SQL)
- rowserrcheck # not relevant (SQL)
- execinquery # not relevant (SQL)
- cyclop # duplicate of gocyclo - cyclop # duplicate of gocyclo
- bodyclose # Too many false positives: https://github.com/timakin/bodyclose/issues/30 - bodyclose # Too many false positives: https://github.com/timakin/bodyclose/issues/30
- dupl - dupl
@@ -52,6 +61,9 @@ linters:
- gomnd - gomnd
- forbidigo - forbidigo
- varnamelen - varnamelen
- wastedassign # is disabled because of generics
- gofumpt
- gci
issues: issues:
exclude-use-default: false exclude-use-default: false
+28 -22
View File
@@ -1,4 +1,6 @@
package crowdsec_bouncer_traefik_plugin // Package crowdsec_bouncer_traefik_plugin implements a middleware that communicates with crowdsec.
// It can cache results to filesystem or redis, or even ask crowdsec for every requests.
package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
import ( import (
"bytes" "bytes"
@@ -15,7 +17,7 @@ import (
cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache" cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache"
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/redis" simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/simpleredis"
) )
const ( const (
@@ -28,6 +30,7 @@ const (
cacheTimeoutKey = "updated" cacheTimeoutKey = "updated"
) )
//nolint:gochecknoglobals
var ( var (
crowdsecStreamHealthy = false crowdsecStreamHealthy = false
ticker chan bool ticker chan bool
@@ -61,8 +64,8 @@ func CreateConfig() *Config {
CrowdsecLapiKey: "", CrowdsecLapiKey: "",
UpdateIntervalSeconds: 60, UpdateIntervalSeconds: 60,
DefaultDecisionSeconds: 60, DefaultDecisionSeconds: 60,
ForwardedHeadersTrustedIPs: []string{},
ClientTrustedIPs: []string{}, ClientTrustedIPs: []string{},
ForwardedHeadersTrustedIPs: []string{},
ForwardedHeadersCustomName: "X-Forwarded-For", ForwardedHeadersCustomName: "X-Forwarded-For",
RedisCacheEnabled: false, RedisCacheEnabled: false,
RedisCacheHost: "redis:6379", RedisCacheHost: "redis:6379",
@@ -83,8 +86,8 @@ type Bouncer struct {
updateInterval int64 updateInterval int64
defaultDecisionTimeout int64 defaultDecisionTimeout int64
customHeader string customHeader string
serverPoolStrategy *ip.PoolStrategy
clientPoolStrategy *ip.PoolStrategy clientPoolStrategy *ip.PoolStrategy
serverPoolStrategy *ip.PoolStrategy
client *http.Client client *http.Client
} }
@@ -141,41 +144,44 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
} }
// ServeHTTP principal function of plugin. // ServeHTTP principal function of plugin.
//
//nolint:nestif
func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !bouncer.enabled { if !bouncer.enabled {
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
return return
} }
// Here we check for the trusted IPs in the customHeader // Here we check for the trusted IPs in the customHeader
remoteHost, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader) remoteIP, err := ip.GetRemoteIP(req, bouncer.serverPoolStrategy, bouncer.customHeader)
if err != nil { if err != nil {
logger.Info(err.Error()) logger.Error(fmt.Sprintf("ServeHTTP ip:%s %s", remoteIP, err.Error()))
bouncer.next.ServeHTTP(rw, req) rw.WriteHeader(http.StatusForbidden)
return return
} }
trusted, err := bouncer.clientPoolStrategy.Checker.Contains(remoteIP)
trusted, err := bouncer.clientPoolStrategy.Checker.Contains(remoteHost)
if err != nil { if err != nil {
logger.Info(err.Error()) logger.Info(err.Error())
return return
} }
// if our IP is in the trusted list we bypass the next checks // if our IP is in the trusted list we bypass the next checks
logger.Debug(fmt.Sprintf("ServeHTTP ip:%v isTrusted:%v", remoteHost, trusted)) logger.Debug(fmt.Sprintf("ServeHTTP ip:%s isTrusted:%v", remoteIP, trusted))
if trusted { if trusted {
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
return return
} }
// TODO This should be simplified
healthy := crowdsecStreamHealthy healthy := crowdsecStreamHealthy
if bouncer.crowdsecMode != noneMode { if bouncer.crowdsecMode != noneMode {
isBanned, err := cache.GetDecision(remoteHost) isBanned, err := cache.GetDecision(remoteIP)
if err != nil { if err != nil {
logger.Debug(err.Error()) logger.Error(err.Error())
if err.Error() == simpleredis.RedisUnreachable { if err.Error() == simpleredis.RedisUnreachable {
healthy = false healthy = false
} }
} else { } else {
logger.Debug(fmt.Sprintf("ServeHTTP cache:hit isBanned:%v", isBanned)) logger.Debug(fmt.Sprintf("ServeHTTP ip:%s cache:hit isBanned:%v", remoteIP, isBanned))
if isBanned { if isBanned {
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
} else { } else {
@@ -193,7 +199,7 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusForbidden) rw.WriteHeader(http.StatusForbidden)
} }
} else { } else {
handleNoStreamCache(bouncer, rw, req, remoteHost) handleNoStreamCache(bouncer, rw, req, remoteIP)
} }
} }
@@ -245,12 +251,12 @@ func startTicker(config *Config, work func()) chan bool {
} }
// We are now in none or live mode. // We are now in none or live mode.
func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Request, remoteHost string) { func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Request, remoteIP string) {
routeURL := url.URL{ routeURL := url.URL{
Scheme: bouncer.crowdsecScheme, Scheme: bouncer.crowdsecScheme,
Host: bouncer.crowdsecHost, Host: bouncer.crowdsecHost,
Path: crowdsecLapiRoute, Path: crowdsecLapiRoute,
RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteHost), RawQuery: fmt.Sprintf("ip=%v&banned=true", remoteIP),
} }
body, err := crowdsecQuery(bouncer, routeURL.String()) body, err := crowdsecQuery(bouncer, routeURL.String())
if err != nil { if err != nil {
@@ -261,7 +267,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
if bytes.Equal(body, []byte("null")) { if bytes.Equal(body, []byte("null")) {
if bouncer.crowdsecMode == liveMode { if bouncer.crowdsecMode == liveMode {
cache.SetDecision(remoteHost, false, bouncer.defaultDecisionTimeout) cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
} }
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
return return
@@ -276,7 +282,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
} }
if len(decisions) == 0 { if len(decisions) == 0 {
if bouncer.crowdsecMode == liveMode { if bouncer.crowdsecMode == liveMode {
cache.SetDecision(remoteHost, false, bouncer.defaultDecisionTimeout) cache.SetDecision(remoteIP, false, bouncer.defaultDecisionTimeout)
} }
bouncer.next.ServeHTTP(rw, req) bouncer.next.ServeHTTP(rw, req)
return return
@@ -288,7 +294,7 @@ func handleNoStreamCache(bouncer *Bouncer, rw http.ResponseWriter, req *http.Req
return return
} }
if bouncer.crowdsecMode == liveMode { if bouncer.crowdsecMode == liveMode {
cache.SetDecision(remoteHost, true, int64(duration.Seconds())) cache.SetDecision(remoteIP, true, int64(duration.Seconds()))
} }
} }
@@ -340,7 +346,7 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) {
req.Header.Add(crowdsecLapiHeader, bouncer.crowdsecKey) req.Header.Add(crowdsecLapiHeader, bouncer.crowdsecKey)
res, err := bouncer.client.Do(req) res, err := bouncer.client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while fetching %v: %s", stringURL, err) return nil, fmt.Errorf("error while fetching %v: %w", stringURL, err)
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error while fetching %v, status code: %d", stringURL, res.StatusCode) return nil, fmt.Errorf("error while fetching %v, status code: %d", stringURL, res.StatusCode)
@@ -348,12 +354,12 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string) ([]byte, error) {
defer func(body io.ReadCloser) { defer func(body io.ReadCloser) {
err = body.Close() err = body.Close()
if err != nil { if err != nil {
logger.Info(fmt.Sprintf("failed to close body reader: %s", err)) logger.Error(fmt.Sprintf("failed to close body reader: %s", err.Error()))
} }
}(res.Body) }(res.Body)
body, err := ioutil.ReadAll(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("error while reading body: %s", err) return nil, fmt.Errorf("error while reading body: %w", err)
} }
return body, nil return body, nil
} }
+1 -1
View File
@@ -1,4 +1,4 @@
package crowdsec_bouncer_traefik_plugin package crowdsec_bouncer_traefik_plugin //nolint:revive,stylecheck
import ( import (
"context" "context"
+23 -14
View File
@@ -1,3 +1,5 @@
// Package cache implements utility routines for manipulating cache.
// It supports currently local file and redis cache.
package cache package cache
import ( import (
@@ -6,7 +8,7 @@ import (
ttl_map "github.com/leprosus/golang-ttl-map" ttl_map "github.com/leprosus/golang-ttl-map"
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/redis" simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/simpleredis"
) )
const ( const (
@@ -14,12 +16,14 @@ const (
cacheNoBannedValue = "f" cacheNoBannedValue = "f"
) )
var cache = ttl_map.New() //nolint:gochecknoglobals
var redis simpleredis.SimpleRedis var (
cache = ttl_map.New()
redis simpleredis.SimpleRedis
redisEnabled = false
)
var redisEnabled = false // FileSystem Cache
// CLASSIC
func getDecisionLocalCache(clientIP string) (bool, error) { func getDecisionLocalCache(clientIP string) (bool, error) {
banned, isCached := cache.Get(clientIP) banned, isCached := cache.Get(clientIP)
@@ -38,7 +42,7 @@ func deleteDecisionLocalCache(clientIP string) {
cache.Del(clientIP) cache.Del(clientIP)
} }
// REDIS // Redis Cache
func getDecisionRedisCache(clientIP string) (bool, error) { func getDecisionRedisCache(clientIP string) (bool, error) {
banned, err := redis.Get(clientIP) banned, err := redis.Get(clientIP)
@@ -50,14 +54,18 @@ func getDecisionRedisCache(clientIP string) (bool, error) {
} }
func setDecisionRedisCache(clientIP string, value string, duration int64) { func setDecisionRedisCache(clientIP string, value string, duration int64) {
redis.Set(clientIP, []byte(value), duration) if err := redis.Set(clientIP, []byte(value), duration); err != nil {
logger.Error(fmt.Sprintf("cache:setDecisionRedisCache %s", err.Error()))
}
} }
func deleteDecisionRedisCache(clientIP string) { func deleteDecisionRedisCache(clientIP string) {
redis.Del(clientIP) if err := redis.Del(clientIP); err != nil {
logger.Error(fmt.Sprintf("cache:deleteDecisionRedisCache %s", err.Error()))
}
} }
// DeleteDecision delete decision in cache // DeleteDecision delete decision in cache.
func DeleteDecision(clientIP string) { func DeleteDecision(clientIP string) {
if redisEnabled { if redisEnabled {
deleteDecisionRedisCache(clientIP) deleteDecisionRedisCache(clientIP)
@@ -71,15 +79,15 @@ func DeleteDecision(clientIP string) {
func GetDecision(clientIP string) (bool, error) { func GetDecision(clientIP string) (bool, error) {
if redisEnabled { if redisEnabled {
return getDecisionRedisCache(clientIP) return getDecisionRedisCache(clientIP)
} else { }
return getDecisionLocalCache(clientIP) return getDecisionLocalCache(clientIP)
} }
}
// SetDecision update the cache with the IP as key and the value banned / not banned.
func SetDecision(clientIP string, isBanned bool, duration int64) { func SetDecision(clientIP string, isBanned bool, duration int64) {
var value string var value string
if isBanned { if isBanned {
logger.Debug(fmt.Sprintf("%v banned", clientIP)) logger.Debug(fmt.Sprintf("cache:SetDecision ip:%v banned", clientIP))
value = cacheBannedValue value = cacheBannedValue
} else { } else {
value = cacheNoBannedValue value = cacheNoBannedValue
@@ -91,8 +99,9 @@ func SetDecision(clientIP string, isBanned bool, duration int64) {
} }
} }
// InitRedisClient loads variables.
func InitRedisClient(host string) { func InitRedisClient(host string) {
redisEnabled = true redisEnabled = true
redis.Init(host) redis.Init(host)
logger.Debug("Redis initialized") logger.Debug("cache:InitRedisClient redis:initialized")
} }
+2
View File
@@ -1,3 +1,5 @@
// Package ip implements utility routines for to manipulates IP and CIDR.
// It allows to find on IP on a list, and find if an IP is part of a list of CIDR.
package ip package ip
import ( import (
+15 -11
View File
@@ -1,3 +1,5 @@
// Package logger implements utility routines to write to stdout and stderr.
// It supports debug, info and error level
package logger package logger
import ( import (
@@ -7,29 +9,31 @@ import (
) )
var ( var (
loggerInfo = log.New(io.Discard, "INFO: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) loggerInfo = log.New(io.Discard, "INFO: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
loggerDebug = log.New(io.Discard, "DEBUG: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) loggerDebug = log.New(io.Discard, "DEBUG: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
loggerError = log.New(io.Discard, "ERROR: CrowdsecBouncerTraefikPlugin: ", log.Ldate|log.Ltime) //nolint:gochecknoglobals
) )
// Init Set Default log level to info in case log level to defined // Init Set Default log level to info in case log level to defined.
func Init(logLevel string) { func Init(logLevel string) {
switch logLevel { loggerError.SetOutput(os.Stderr)
case "INFO":
loggerInfo.SetOutput(os.Stdout)
case "DEBUG":
loggerInfo.SetOutput(os.Stdout) loggerInfo.SetOutput(os.Stdout)
if logLevel == "DEBUG" {
loggerDebug.SetOutput(os.Stdout) loggerDebug.SetOutput(os.Stdout)
default:
loggerInfo.SetOutput(os.Stdout)
} }
} }
// Info Log info // Info log to Stdout.
func Info(str string) { func Info(str string) {
loggerInfo.Printf(str) loggerInfo.Printf(str)
} }
// Info Log debug // Debug log to Stdout.
func Debug(str string) { func Debug(str string) {
loggerDebug.Printf(str) loggerDebug.Printf(str)
} }
// Error log to Stderr.
func Error(str string) {
loggerError.Printf(str)
}
@@ -1,3 +1,6 @@
// Package simpleredis implements utility routines for interacting.
// It supports currently the following operations: GET, SET, DELETE,
// and support timetoleave for keys.
package simpleredis package simpleredis
import ( import (
@@ -11,12 +14,14 @@ import (
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
) )
// Error strings for redis.
const ( const (
RedisUnreachable = "redis:unreachable" RedisUnreachable = "redis:unreachable"
RedisMiss = "redis:miss" RedisMiss = "redis:miss"
RedisTimeout = "redis:timeout" RedisTimeout = "redis:timeout"
) )
// A RedisCmd is used to communicate with redis at low level using commands.
type RedisCmd struct { type RedisCmd struct {
Command string Command string
Name string Name string
@@ -25,6 +30,7 @@ type RedisCmd struct {
Error error Error error
} }
// A SimpleRedis is used to communicate with redis.
type SimpleRedis struct { type SimpleRedis struct {
redisHost string redisHost string
} }
@@ -39,6 +45,14 @@ func genRedisArray(params ...[]byte) []byte {
return []byte(MSG) return []byte(MSG)
} }
func send(wr *textproto.Writer, method string, data []byte) {
if err := wr.PrintfLine(string(data)); err != nil {
logger.Error(fmt.Sprintf("redis:%s %s", method, err.Error()))
} else {
logger.Debug(fmt.Sprintf("redis:%s", method))
}
}
func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) { func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
dialer := net.Dialer{Timeout: 2 * time.Second} dialer := net.Dialer{Timeout: 2 * time.Second}
conn, err := dialer.Dial("tcp", hostnamePort) conn, err := dialer.Dial("tcp", hostnamePort)
@@ -46,24 +60,25 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
channel <- RedisCmd{Error: fmt.Errorf(RedisUnreachable)} channel <- RedisCmd{Error: fmt.Errorf(RedisUnreachable)}
return return
} }
defer conn.Close() defer func() {
if err := conn.Close(); err != nil {
logger.Error(fmt.Sprintf("redis:connClose %s", err.Error()))
}
}()
writer := textproto.NewWriter(bufio.NewWriter(conn)) writer := textproto.NewWriter(bufio.NewWriter(conn))
reader := textproto.NewReader(bufio.NewReader(conn)) reader := textproto.NewReader(bufio.NewReader(conn))
switch cmd.Command { switch cmd.Command {
case "SET": case "SET":
data := genRedisArray([]byte("SET"), []byte(cmd.Name), []byte(cmd.Data), []byte("EX"), []byte(fmt.Sprintf("%v", cmd.Duration))) data := genRedisArray([]byte("SET"), []byte(cmd.Name), cmd.Data, []byte("EX"), []byte(fmt.Sprintf("%d", cmd.Duration)))
writer.PrintfLine(string(data)) send(writer, "set", data)
logger.Debug("redis:set")
case "DEL": case "DEL":
data := genRedisArray([]byte("DEL"), []byte(cmd.Name)) data := genRedisArray([]byte("DEL"), []byte(cmd.Name))
writer.PrintfLine(string(data)) send(writer, "del", data)
logger.Debug("redis:del")
case "GET": case "GET":
data := genRedisArray([]byte("GET"), []byte(cmd.Name)) data := genRedisArray([]byte("GET"), []byte(cmd.Name))
writer.PrintfLine(string(data)) send(writer, "get", data)
logger.Debug("redis:get")
for { for {
select { select {
case <-time.After(time.Second * 1): case <-time.After(time.Second * 1):
@@ -83,10 +98,12 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
} }
} }
// Init sets the redisHost used to connect to redis.
func (sr *SimpleRedis) Init(redisHost string) { func (sr *SimpleRedis) Init(redisHost string) {
sr.redisHost = redisHost sr.redisHost = redisHost
} }
// Get fetches the value for key name in redis.
func (sr *SimpleRedis) Get(name string) ([]byte, error) { func (sr *SimpleRedis) Get(name string) ([]byte, error) {
redisCmd := RedisCmd{ redisCmd := RedisCmd{
Command: "GET", Command: "GET",
@@ -101,6 +118,7 @@ func (sr *SimpleRedis) Get(name string) ([]byte, error) {
return resp.Data, nil return resp.Data, nil
} }
// Set update the value for key name in redis with value data for duration.
func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error { func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
redisCmd := RedisCmd{ redisCmd := RedisCmd{
Command: "SET", Command: "SET",
@@ -112,6 +130,7 @@ func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
return nil return nil
} }
// Del remove the key name in redis.
func (sr *SimpleRedis) Del(name string) error { func (sr *SimpleRedis) Del(name string) error {
redisCmd := RedisCmd{ redisCmd := RedisCmd{
Command: "DEL", Command: "DEL",