Compare commits

..

No commits in common. "master" and "v0.4" have entirely different histories.
master ... v0.4

3 changed files with 97 additions and 309 deletions

View File

@ -10,7 +10,6 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"golang.org/x/net/proxy"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
@ -19,12 +18,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"
) )
@ -67,11 +66,8 @@ 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
@ -95,35 +91,31 @@ 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() {
@ -138,7 +130,6 @@ 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() {
@ -174,135 +165,56 @@ 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)
errchan := make(chan error) c := make(chan string)
eofchan := make(chan string)
metachan := make(chan string)
// Launching the Rate limiters // Launching the Rate limiter
rateLimiter := time.Tick(d4.rate) ratelimiter := time.Tick(d4.rate)
retryLimiter := time.Tick(d4.retry)
//Setup d4.mhb = bytes.NewBuffer(d4.mh)
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.")
}
// force is a flag that forces the creation of a new connection
force := false
// On the first run, we send d4 meta header for type 2/254
if d4.conf.ttype == 254 || d4.conf.ttype == 2 {
go sendMeta(d4p, errchan, metachan)
H:
for {
select {
case <-errchan:
select {
case <-retryLimiter:
go sendMeta(d4p, errchan, metachan)
case <-s:
logger.Println("Exiting")
exit(d4p, 0)
}
case <-metachan:
break H
}
}
}
// Launch copy routine
go d4Copy(d4p, errchan, eofchan)
// Handle signals
for { for {
// init or reinit after retry
if set(d4p) {
// type 254 requires to send a meta-header first
if d4.conf.ttype == 254 || d4.conf.ttype == 2 {
// create a jsonreader
d4p.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 {
panic(fmt.Sprintf("Cannot initiate session %s", err))
}
logger.Println(fmt.Sprintf("Meta-Header sent: %d bytes", nread))
d4p.dst.restoreHeader()
}
// 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
select { select {
// Case where the input ran out of data to consume1 case <-c:
case <-eofchan: continue
// 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)
@ -319,31 +231,25 @@ func exit(d4 *d4S, exitcode int) {
os.Exit(exitcode) os.Exit(exitcode)
} }
func d4Copy(d4 *d4S, errchan chan error, eofchan chan string) { func set(d4 *d4S) bool {
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 {
logger.Printf("D4copy: %s", err) c <- fmt.Sprintf("D4copy: %s", err)
errchan <- err
return return
} }
eofchan <- fmt.Sprintf("EOF: Nread: %d", nread) c <- 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
} }
@ -358,20 +264,9 @@ 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 := string(readConfFile(d4, "redis_d4")) tmp := config.ReadConfigFile(*confdir, "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")
@ -445,13 +340,9 @@ 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.")
@ -498,7 +389,7 @@ func newD4Writer(writer io.Writer, key []byte) d4Writer {
} }
// TODO QUICK IMPLEM, REVISE // TODO QUICK IMPLEM, REVISE
func setReaderWriters(d4 *d4S, force bool) bool { func setReaderWriters(d4 *d4S) 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":
@ -515,85 +406,42 @@ func setReaderWriters(d4 *d4S, force bool) 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) (*d4).src, err = inputreader.NewLPOPReader(&(*d4).redisCon, (*d4).conf.redisDB, (*d4).conf.redisQueue, int(time.Second*(*d4).retry))
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 {
// We test whether a connection already exist dial := net.Dialer{
// (case where the reader run out of data) Timeout: (*d4).ct,
// force forces to reset the connections after KeepAlive: (*d4).cka,
// failure to reuse it FallbackDelay: 0,
if _, ok := (*d4).dst.w.(net.Conn); !ok || force { }
if (*d4).tor { tlsc := tls.Config{
dialer := net.Dialer{ InsecureSkipVerify: true,
Timeout: (*d4).ct, }
KeepAlive: (*d4).cka, if (*d4).cc {
FallbackDelay: 0, tlsc = tls.Config{
} InsecureSkipVerify: false,
dial, err := proxy.SOCKS5("tcp", "127.0.0.1:9050", nil, &dialer) RootCAs: &(*d4).ca,
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":
@ -618,7 +466,7 @@ func generateUUIDv4() []byte {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
logger.Println(fmt.Sprintf("UUIDv4: %s", uuid)) logger.Println(fmt.Sprintf("UUIDv4: %s\n", uuid))
return uuid.Bytes() return uuid.Bytes()
} }

5
go.mod
View File

@ -3,9 +3,6 @@ module github.com/D4-project/d4-goclient
go 1.13 go 1.13
require ( require (
github.com/D4-project/d4-golang-utils v0.1.14 github.com/D4-project/d4-golang-utils v0.1.3
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,63 +1,6 @@
github.com/D4-project/d4-golang-utils v0.1.14 h1:APwN+i4qyDrxT8gvlbeV/VXfNas2GvPWOnkTGX1K2Lo= github.com/D4-project/d4-golang-utils v0.1.3 h1:JrGoQ3Va4SwGl8Pjnb2bK/wxdFaGsjulAmp0bxZO7jg=
github.com/D4-project/d4-golang-utils v0.1.14/go.mod h1:qXVZ3kCL72i3uYe29t7aEy9dU0bNqtFvcoNE1dJu0zo= github.com/D4-project/d4-golang-utils v0.1.3/go.mod h1:GGR5KMhvABZtIfmS5jZkwQnBoP+9/V0ZEETSGiWLaM4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
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=