Compare commits

..

25 Commits
v0.4 ... master

Author SHA1 Message Date
Jean-Louis Huynen 12cdeabfb6
Merge pull request #21 from D4-project/dependabot/go_modules/golang.org/x/net-0.17.0
build(deps): bump golang.org/x/net from 0.12.0 to 0.17.0
2023-10-12 08:01:55 +02:00
dependabot[bot] 3db2573aa7
build(deps): bump golang.org/x/net from 0.12.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.12.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-11 22:52:51 +00:00
Jean-Louis Huynen f36fce9950
chg: [modules] bump modules 2023-07-07 14:47:04 +02:00
Jean-Louis Huynen 44858060c2
Merge pull request #19 from D4-project/dependabot/go_modules/golang.org/x/net-0.7.0
build(deps): bump golang.org/x/net from 0.0.0-20210119194325-5f4716e94777 to 0.7.0
2023-02-27 07:35:36 +01:00
dependabot[bot] 13b3183eec
build(deps): bump golang.org/x/net
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20210119194325-5f4716e94777 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-25 02:17:49 +00:00
Jean-Louis Huynen f1a5bc2c14
chg: [filewatcher] daily rotation of watched folder - fixed 2021-03-03 12:10:46 +01:00
Jean-Louis Huynen daf9d72347
chg: [filewatcher] daily rotation of watched folder 2021-03-02 14:49:20 +01:00
Jean-Louis Huynen 7a614f706e
fix: [filerwatcher] fix silly path bug + bump glu 0.1.12 2021-02-19 10:44:11 +01:00
Jean-Louis Huynen d015ee6388
add: [torproxy] Use tor proxy on 9050 2021-02-19 09:35:58 +01:00
Jean-Louis Huynen 879bcb6231
chg: [filerwatcher] bump golangutils 2021-02-18 15:24:02 +01:00
Jean-Louis Huynen 856ba6db6b
add: [filerwatcher] go modules + fmt 2021-02-17 16:42:04 +01:00
Jean-Louis Huynen a886c5f82f
add: [filerwatcher] base64 or json files 2021-02-16 11:30:03 +01:00
Jean-Louis Huynen 7fc1a1b0c0
add: [filerwatcher] fix error messages 2021-02-16 10:43:01 +01:00
Jean-Louis Huynen afc9526219
add: [filerwatcher] initial work on file watcher 2021-02-15 16:18:57 +01:00
Jean-Louis Huynen 1bc27e9c65
del: [import] duplicate 2020-07-28 10:21:16 +02:00
Jean-Louis Huynen ef0599a323
chg: [mod] bump d4-golang-util - fix #13 2020-06-19 11:56:28 +02:00
Jean-Louis Huynen 74d0b72f6b chg: [init] new init/reset/signal logic 2020-06-19 11:34:04 +02:00
Jean-Louis Huynen 4b3028688f
chg: [deps] adapt and bump to d4-golang-utils v0.1.5 2020-05-27 17:12:43 +02:00
Jean-Louis Huynen 88269e3cb7
Merge pull request #16 from D4-project/tcpreuse
chg: [main] tear down old connections for type 2
2020-05-26 17:25:00 +02:00
Jean-Louis Huynen bdf5e13e1e
chg: [main] tear down old connections for type 2 2020-05-26 17:18:48 +02:00
Jean-Louis Huynen 21256a2ec8
Merge pull request #15 from D4-project/tcpreuse
Tcpreuse
2020-05-26 16:18:18 +02:00
Jean-Louis Huynen 0c1cbbbe52
chg: [main] remove useless log entry 2020-05-26 16:16:29 +02:00
Jean-Louis Huynen a7032f58ee
chg: [main] listen to OS signal when ratelimiting 2020-05-26 16:12:50 +02:00
Jean-Louis Huynen ab248fa3ad
chg: [main] reuse existing tcp connection 2020-05-26 15:37:33 +02:00
Jean-Louis Huynen d52c02f8de
chg: [mod] bump d4-golang-utils 2020-05-25 16:51:05 +02:00
3 changed files with 306 additions and 94 deletions

View File

@ -10,6 +10,7 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/net/proxy"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
@ -18,12 +19,12 @@ import (
"os/signal" "os/signal"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
config "github.com/D4-project/d4-golang-utils/config" config "github.com/D4-project/d4-golang-utils/config"
uuid "github.com/D4-project/d4-golang-utils/crypto/hash" uuid "github.com/D4-project/d4-golang-utils/crypto/hash"
"github.com/D4-project/d4-golang-utils/inputreader" "github.com/D4-project/d4-golang-utils/inputreader"
_ "github.com/D4-project/d4-golang-utils/inputreader"
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
) )
@ -66,8 +67,11 @@ type (
ct time.Duration ct time.Duration
ce bool ce bool
retry time.Duration retry time.Duration
rate time.Duration rate time.Duration
cc bool cc bool
tor bool
daily bool
json bool
ca x509.CertPool ca x509.CertPool
d4error uint8 d4error uint8
errnoCopy uint8 errnoCopy uint8
@ -91,31 +95,35 @@ type (
redisPort string redisPort string
redisQueue string redisQueue string
redisDB int redisDB int
folderstr string
} }
) )
var ( var (
// Verbose mode and logging // Verbose mode and logging
buf bytes.Buffer buf bytes.Buffer
logger = log.New(&buf, "INFO: ", log.Lshortfile) logger = log.New(&buf, "INFO: ", log.Lshortfile)
debugger = log.New(&buf, "DEBUG: ", log.Lmicroseconds) debugger = log.New(&buf, "DEBUG: ", log.Lmicroseconds)
debugf = func(debug string) { debugf = func(debug string) {
debugger.Println("", debug) debugger.Println("", debug)
} }
tmpct, _ = time.ParseDuration("5mn") tmpct, _ = time.ParseDuration("5mn")
tmpcka, _ = time.ParseDuration("30s") tmpcka, _ = time.ParseDuration("30s")
tmpretry, _ = time.ParseDuration("30s") tmpretry, _ = time.ParseDuration("30s")
tmprate, _ = time.ParseDuration("200ms") tmprate, _ = time.ParseDuration("200ms")
confdir = flag.String("c", "", "configuration directory") confdir = flag.String("c", "", "configuration directory")
debug = flag.Bool("v", false, "Set to True, true, TRUE, 1, or t to enable verbose output on stdout - Don't use in production") debug = flag.Bool("v", false, "Set to True, true, TRUE, 1, or t to enable verbose output on stdout - Don't use in production")
ce = flag.Bool("ce", true, "Set to True, true, TRUE, 1, or t to enable TLS on network destination") ce = flag.Bool("ce", true, "Set to True, true, TRUE, 1, or t to enable TLS on network destination")
ct = flag.Duration("ct", tmpct, "Set timeout in human format") ct = flag.Duration("ct", tmpct, "Set timeout in human format")
cka = flag.Duration("cka", tmpcka, "Keep Alive time human format, 0 to disable") cka = flag.Duration("cka", tmpcka, "Keep Alive time human format, 0 to disable")
retry = flag.Duration("rt", tmpretry, "Time in human format before retry after connection failure, set to 0 to exit on failure") retry = flag.Duration("rt", tmpretry, "Time in human format before retry after connection failure, set to 0 to exit on failure")
rate = flag.Duration("rl", tmprate, "Rate limiter: time in human format before retry after EOF") rate = flag.Duration("rl", tmprate, "Rate limiter: time in human format before retry after EOF")
cc = flag.Bool("cc", false, "Check TLS certificate against rootCA.crt") cc = flag.Bool("cc", false, "Check TLS certificate against rootCA.crt")
torflag = flag.Bool("tor", false, "Use a SOCKS5 tor proxy on 9050")
dailyflag = flag.Bool("daily", false, "Sets up filewatcher to watch a new %Y%M%D folder at midnight")
jsonflag = flag.Bool("json", false, "The files watched are json files")
) )
func main() { func main() {
@ -130,6 +138,7 @@ func main() {
} }
defer f.Close() defer f.Close()
logger.SetOutput(f) logger.SetOutput(f)
logger.SetFlags(log.LstdFlags | log.Lshortfile)
logger.Println("Init") logger.Println("Init")
flag.Usage = func() { flag.Usage = func() {
@ -165,56 +174,135 @@ func main() {
*confdir = strings.TrimSuffix(*confdir, "/") *confdir = strings.TrimSuffix(*confdir, "/")
*confdir = strings.TrimSuffix(*confdir, "\\") *confdir = strings.TrimSuffix(*confdir, "\\")
} }
d4.confdir = *confdir d4.confdir = *confdir
d4.ce = *ce d4.ce = *ce
d4.ct = *ct d4.ct = *ct
d4.cc = *cc d4.cc = *cc
d4.json = *jsonflag
d4.cka = *cka d4.cka = *cka
d4.retry = *retry d4.retry = *retry
d4.rate = *rate d4.rate = *rate
d4.tor = *torflag
d4.daily = *dailyflag
s := make(chan os.Signal, 1) s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt, os.Kill) signal.Notify(s, os.Interrupt, os.Kill)
c := make(chan string) errchan := make(chan error)
eofchan := make(chan string)
metachan := make(chan string)
// Launching the Rate limiter // Launching the Rate limiters
ratelimiter := time.Tick(d4.rate) rateLimiter := time.Tick(d4.rate)
retryLimiter := time.Tick(d4.retry)
d4.mhb = bytes.NewBuffer(d4.mh) //Setup
if !d4loadConfig(d4p) {
panic("Could not load Config.")
}
if !setReaderWriters(d4p, false) {
panic("Could not Init Inputs Outputs.")
}
if !d4.dst.initHeader(d4p) {
panic("Could not Init Headers.")
}
for { // force is a flag that forces the creation of a new connection
// init or reinit after retry force := false
if set(d4p) {
// type 254 requires to send a meta-header first // On the first run, we send d4 meta header for type 2/254
if d4.conf.ttype == 254 || d4.conf.ttype == 2 { if d4.conf.ttype == 254 || d4.conf.ttype == 2 {
// create a jsonreader go sendMeta(d4p, errchan, metachan)
d4p.dst.hijackHeader() H:
// Ugly hack to skip bytes.Buffer WriteTo check that bypasses my fixed lenght buffer for {
nread, err := io.CopyBuffer(&d4.dst, struct{ io.Reader }{d4.mhb}, d4.dst.pb) select {
if err != nil { case <-errchan:
panic(fmt.Sprintf("Cannot initiate session %s", err)) select {
case <-retryLimiter:
go sendMeta(d4p, errchan, metachan)
case <-s:
logger.Println("Exiting")
exit(d4p, 0)
} }
logger.Println(fmt.Sprintf("Meta-Header sent: %d bytes", nread)) case <-metachan:
d4p.dst.restoreHeader() break H
} }
// copy routine
go d4Copy(d4p, c)
// Block until the rate limiter allow us to continue
<-ratelimiter
} else if d4.retry > 0 {
go func() {
logger.Println(fmt.Sprintf("Sleeping for %.f seconds before retry...", d4.retry.Seconds()))
time.Sleep(d4.retry)
c <- "done waiting"
}()
} else {
exit(d4p, 1)
} }
}
// Block until we catch an event // Launch copy routine
go d4Copy(d4p, errchan, eofchan)
// Handle signals
for {
select { select {
case <-c: // Case where the input ran out of data to consume1
continue case <-eofchan:
// We wait for ratelimiter before polling again
EOF:
for {
select {
case <-rateLimiter:
// copy routine
go d4Copy(d4p, errchan, eofchan)
break EOF
// Exit signal
case <-s:
logger.Println("Exiting")
exit(d4p, 0)
}
}
// ERROR, we check first whether it is network related
case err := <-errchan:
// On connection errors, we force setReaderWriter to reset the connection
force = false
switch t := err.(type) {
case *net.OpError:
force = true
if t.Op == "dial" {
logger.Println("Unknown Host")
} else if t.Op == "read" {
logger.Println("Connection Refused")
} else if t.Op == "write" {
logger.Println("Write error")
}
case syscall.Errno:
if t == syscall.ECONNREFUSED {
force = true
logger.Println("Connection Refused")
}
}
// We wait for retryLimiter before writing again
RETRY:
for {
select {
case <-retryLimiter:
if !setReaderWriters(d4p, force) {
// Can't connect, we break to retry
// force is still true
break
}
if !d4.dst.initHeader(d4p) {
panic("Could not Init Headers.")
}
if (d4.conf.ttype == 254 || d4.conf.ttype == 2) && force {
// setReaderWriter is happy, we should have a working
// connection from now on.
force = false
// Sending meta header for the first time on this new connection
go sendMeta(d4p, errchan, metachan)
}
break RETRY
// Exit signal
case <-s:
logger.Println("Exiting")
exit(d4p, 0)
}
}
// metaheader sent, launch the copy routine
case <-metachan:
go d4Copy(d4p, errchan, eofchan)
// Exit signal
case <-s: case <-s:
logger.Println("Exiting") logger.Println("Exiting")
exit(d4p, 0) exit(d4p, 0)
@ -231,25 +319,31 @@ func exit(d4 *d4S, exitcode int) {
os.Exit(exitcode) os.Exit(exitcode)
} }
func set(d4 *d4S) bool { func d4Copy(d4 *d4S, errchan chan error, eofchan chan string) {
if d4loadConfig(d4) {
if setReaderWriters(d4) {
if d4.dst.initHeader(d4) {
return true
}
}
}
return false
}
func d4Copy(d4 *d4S, c chan string) {
nread, err := io.CopyBuffer(&d4.dst, d4.src, d4.dst.pb) nread, err := io.CopyBuffer(&d4.dst, d4.src, d4.dst.pb)
// Always retry // Always retry
if err != nil { if err != nil {
c <- fmt.Sprintf("D4copy: %s", err) logger.Printf("D4copy: %s", err)
errchan <- err
return return
} }
c <- fmt.Sprintf("EOF: Nread: %d", nread) eofchan <- fmt.Sprintf("EOF: Nread: %d", nread)
}
func sendMeta(d4 *d4S, errchan chan error, metachan chan string) {
// Fill metaheader buffer with metaheader data
d4.mhb = bytes.NewBuffer(d4.mh)
d4.dst.hijackHeader()
// Ugly hack to skip bytes.Buffer WriteTo check that bypasses my fixed lenght buffer
nread, err := io.CopyBuffer(&d4.dst, struct{ io.Reader }{d4.mhb}, d4.dst.pb)
if err != nil {
logger.Printf("Cannot sent meta-header: %s", err)
errchan <- err
return
}
logger.Println(fmt.Sprintf("Meta-Header sent: %d bytes", nread))
d4.dst.restoreHeader()
metachan <- "Header Sent"
return return
} }
@ -264,9 +358,20 @@ func d4loadConfig(d4 *d4S) bool {
if len((*d4).conf.source) < 1 { if len((*d4).conf.source) < 1 {
log.Fatal("Unsupported source") log.Fatal("Unsupported source")
} }
if (*d4).conf.source == "folder" {
fstr := string(readConfFile(d4, "folder"))
if ffd, err := os.Stat(fstr); os.IsNotExist(err) {
log.Fatal("Folder does not exist")
} else {
if !ffd.IsDir() {
log.Fatal("Folder is not a directory")
}
}
(*d4).conf.folderstr = fstr
}
if (*d4).conf.source == "d4server" { if (*d4).conf.source == "d4server" {
// Parse Input Redis Config // Parse Input Redis Config
tmp := config.ReadConfigFile(*confdir, "redis_d4") tmp := string(readConfFile(d4, "redis_d4"))
ss := strings.Split(string(tmp), "/") ss := strings.Split(string(tmp), "/")
if len(ss) <= 1 { if len(ss) <= 1 {
log.Fatal("Missing Database in Redis input config: should be host:port/database_name") log.Fatal("Missing Database in Redis input config: should be host:port/database_name")
@ -340,9 +445,13 @@ func d4loadConfig(d4 *d4S) bool {
if off, err := file.Seek(0, 0); err != nil || off != 0 { if off, err := file.Seek(0, 0); err != nil || off != 0 {
panic(fmt.Sprintf("Cannot read Meta-Header file: %s", err)) panic(fmt.Sprintf("Cannot read Meta-Header file: %s", err))
} else { } else {
// create metaheader buffer
d4.mhb = bytes.NewBuffer(d4.mh)
if err := json.Compact((*d4).mhb, data[:count]); err != nil { if err := json.Compact((*d4).mhb, data[:count]); err != nil {
logger.Println("Failed to compact meta header file") logger.Println("Failed to compact meta header file")
} }
// Store the metaheader in d4 struct for subsequent retries
(*d4).mh = data[:count]
} }
} else { } else {
panic("A Meta-Header File should at least contain a 'type' field.") panic("A Meta-Header File should at least contain a 'type' field.")
@ -389,7 +498,7 @@ func newD4Writer(writer io.Writer, key []byte) d4Writer {
} }
// TODO QUICK IMPLEM, REVISE // TODO QUICK IMPLEM, REVISE
func setReaderWriters(d4 *d4S) bool { func setReaderWriters(d4 *d4S, force bool) bool {
//TODO implement other destination file, fifo unix_socket ... //TODO implement other destination file, fifo unix_socket ...
switch (*d4).conf.source { switch (*d4).conf.source {
case "stdin": case "stdin":
@ -406,42 +515,85 @@ func setReaderWriters(d4 *d4S) bool {
logger.Println("Could not connect to d4 Redis") logger.Println("Could not connect to d4 Redis")
return false return false
} }
(*d4).src, err = inputreader.NewLPOPReader(&(*d4).redisCon, (*d4).conf.redisDB, (*d4).conf.redisQueue, int(time.Second*(*d4).retry)) (*d4).src, err = inputreader.NewLPOPReader(&(*d4).redisCon, (*d4).conf.redisDB, (*d4).conf.redisQueue)
if err != nil { if err != nil {
log.Printf("Could not create d4 Redis Descriptor %q \n", err) log.Printf("Could not create d4 Redis Descriptor %q \n", err)
return false return false
} }
case "folder":
var err error
(*d4).src, err = inputreader.NewFileWatcherReader((*d4).conf.folderstr, (*d4).json, (*d4).daily, logger)
if err != nil {
log.Printf("Could not create File Watcher %q \n", err)
return false
}
} }
isn, dstnet := config.IsNet((*d4).conf.destination) isn, dstnet := config.IsNet((*d4).conf.destination)
if isn { if isn {
dial := net.Dialer{ // We test whether a connection already exist
Timeout: (*d4).ct, // (case where the reader run out of data)
KeepAlive: (*d4).cka, // force forces to reset the connections after
FallbackDelay: 0, // failure to reuse it
} if _, ok := (*d4).dst.w.(net.Conn); !ok || force {
tlsc := tls.Config{ if (*d4).tor {
InsecureSkipVerify: true, dialer := net.Dialer{
} Timeout: (*d4).ct,
if (*d4).cc { KeepAlive: (*d4).cka,
tlsc = tls.Config{ FallbackDelay: 0,
InsecureSkipVerify: false, }
RootCAs: &(*d4).ca, dial, err := proxy.SOCKS5("tcp", "127.0.0.1:9050", nil, &dialer)
if err != nil {
log.Fatal(err)
}
tlsc := tls.Config{
InsecureSkipVerify: true,
}
if (*d4).cc {
tlsc = tls.Config{
InsecureSkipVerify: false,
RootCAs: &(*d4).ca,
}
}
conn, errc := dial.Dial("tcp", dstnet)
if errc != nil {
logger.Println(errc)
return false
}
if (*d4).ce == true {
conn = tls.Client(conn, &tlsc) // use tls
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
} else {
dial := net.Dialer{
Timeout: (*d4).ct,
KeepAlive: (*d4).cka,
FallbackDelay: 0,
}
tlsc := tls.Config{
InsecureSkipVerify: true,
}
if (*d4).cc {
tlsc = tls.Config{
InsecureSkipVerify: false,
RootCAs: &(*d4).ca,
}
}
if (*d4).ce == true {
conn, errc := tls.DialWithDialer(&dial, "tcp", dstnet, &tlsc)
if errc != nil {
logger.Println(errc)
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
} else {
conn, errc := dial.Dial("tcp", dstnet)
if errc != nil {
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
}
} }
} }
if (*d4).ce == true {
conn, errc := tls.DialWithDialer(&dial, "tcp", dstnet, &tlsc)
if errc != nil {
logger.Println(errc)
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
} else {
conn, errc := dial.Dial("tcp", dstnet)
if errc != nil {
return false
}
(*d4).dst = newD4Writer(conn, (*d4).conf.key)
}
} else { } else {
switch (*d4).conf.destination { switch (*d4).conf.destination {
case "stdout": case "stdout":
@ -466,7 +618,7 @@ func generateUUIDv4() []byte {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
logger.Println(fmt.Sprintf("UUIDv4: %s\n", uuid)) logger.Println(fmt.Sprintf("UUIDv4: %s", uuid))
return uuid.Bytes() return uuid.Bytes()
} }

5
go.mod
View File

@ -3,6 +3,9 @@ module github.com/D4-project/d4-goclient
go 1.13 go 1.13
require ( require (
github.com/D4-project/d4-golang-utils v0.1.3 github.com/D4-project/d4-golang-utils v0.1.14
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/gomodule/redigo v2.0.0+incompatible github.com/gomodule/redigo v2.0.0+incompatible
github.com/rjeczalik/notify v0.9.3 // indirect
golang.org/x/net v0.17.0
) )

63
go.sum
View File

@ -1,6 +1,63 @@
github.com/D4-project/d4-golang-utils v0.1.3 h1:JrGoQ3Va4SwGl8Pjnb2bK/wxdFaGsjulAmp0bxZO7jg= github.com/D4-project/d4-golang-utils v0.1.14 h1:APwN+i4qyDrxT8gvlbeV/VXfNas2GvPWOnkTGX1K2Lo=
github.com/D4-project/d4-golang-utils v0.1.3/go.mod h1:GGR5KMhvABZtIfmS5jZkwQnBoP+9/V0ZEETSGiWLaM4= github.com/D4-project/d4-golang-utils v0.1.14/go.mod h1:qXVZ3kCL72i3uYe29t7aEy9dU0bNqtFvcoNE1dJu0zo=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redislabs/redisgraph-go v2.0.2+incompatible/go.mod h1:GYn4oUFkbkHx49xm2H4G8jZziqKDKdRtDUuTBZTmqBE=
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=