From acfc03e390fd7c86217537af9d86fea0218f08df Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Sun, 10 Dec 2023 18:44:36 +1000 Subject: [PATCH 1/2] Completely replace RabbitMQ / AMQP with a PG based job queue server --- README.md | 5 +- api/handlers.go | 62 +- api/main.go | 28 +- common/config.go | 47 +- common/config_types.go | 11 - common/cypress.go | 4 +- common/errorcodes.go | 18 +- common/licences.go | 2 +- common/live.go | 560 +++--- common/live_types.go | 156 +- common/memcache.go | 2 +- common/minio.go | 19 +- common/postgresql.go | 41 +- common/postgresql_live.go | 637 +++++++ common/responses.go | 2 +- common/sqlite.go | 26 +- common/util.go | 2 +- .../000005_job_submission_tables.down.sql | 8 + .../000005_job_submission_tables.up.sql | 58 + db4s/main.go | 5 +- docker/Dockerfile | 27 +- docker/README.md | 5 +- docker/config.toml | 8 - go.mod | 3 +- go.sum | 1615 ----------------- live/main.go | 434 +---- package.json | 7 +- standalone/analysis/main.go | 6 +- webui/execute.go | 4 +- webui/main.go | 42 +- webui/pages.go | 6 +- webui/vis.go | 2 +- 32 files changed, 1202 insertions(+), 2650 deletions(-) create mode 100644 common/postgresql_live.go create mode 100644 database/migrations/000005_job_submission_tables.down.sql create mode 100644 database/migrations/000005_job_submission_tables.up.sql diff --git a/README.md b/README.md index fca931014..6875ddbaa 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,11 @@ our API server at https://api.dbhub.io, or run things locally for your own users ### Requirements -* [Golang](https://golang.org) - version 1.17 or above is required. +* [Golang](https://golang.org) - version 1.18 or above is required. * [Memcached](https://memcached.org) - version 1.4.33 and above are known to work. * [Minio](https://minio.io) - release 2016-11-26T02:23:47Z and later are known to work. -* [NodeJS](https://nodejs.org) - version 18.x is known to work, others are untested. +* [NodeJS](https://nodejs.org) - version 20 is known to work, others are untested. * [PostgreSQL](https://www.postgresql.org) - version 13 and above are known to work. -* [RabbitMQ](https://www.rabbitmq.com) - version 3.10.x and above are known to work. * [Yarn](https://classic.yarnpkg.com) - version 1.22.x. Not Yarn 2.x or greater. ### Subdirectories diff --git a/api/handlers.go b/api/handlers.go index 67dcd0e2a..9908bf3e5 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "errors" "fmt" "log" "mime/multipart" @@ -132,11 +131,11 @@ func columnsHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } - // If it's a standard database, process it locally. Else send the query to our AMQP backend + // If it's a standard database, process it locally. Else send the query to our job queue backend var cols []sqlite.Column if !isLive { // Get Minio bucket and object id for the SQLite file @@ -187,7 +186,7 @@ func columnsHandler(w http.ResponseWriter, r *http.Request) { return } } else { - // Send the columns request to our AMQP backend + // Send the columns request to our job queue backend cols, _, err = com.LiveColumns(liveNode, loggedInUser, dbOwner, dbName, table) if err != nil { jsonErr(w, err.Error(), http.StatusBadRequest) @@ -383,7 +382,7 @@ func deleteHandler(w http.ResponseWriter, r *http.Request) { } } - // For a live database, delete it from both Minio and our AMQP backend + // For a live database, delete it from both Minio and our job queue backend var bucket, id string if isLive { // Get the Minio bucket and object names for this database @@ -400,7 +399,7 @@ func deleteHandler(w http.ResponseWriter, r *http.Request) { return } - // Delete the database from our AMQP backend + // Delete the database from our job queue backend err = com.LiveDelete(liveNode, loggedInUser, dbOwner, dbName) if err != nil { jsonErr(w, err.Error(), http.StatusInternalServerError) @@ -700,11 +699,11 @@ func executeHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } - // Send the SQL execution request to our AMQP backend + // Send the SQL execution request to our job queue backend var rowsChanged int rowsChanged, err = com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql) if err != nil { @@ -752,11 +751,11 @@ func indexesHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } - // If it's a standard database, process it locally. Else send the query to our AMQP backend + // If it's a standard database, process it locally. Else send the query to our job queue backend var indexes []com.APIJSONIndex if !isLive { // Get Minio bucket and object id for the SQLite file @@ -811,33 +810,12 @@ func indexesHandler(w http.ResponseWriter, r *http.Request) { indexes = append(indexes, oneIndex) } } else { - // Send the indexes request to our AMQP backend - var rawResponse []byte - rawResponse, err = com.MQRequest(com.AmqpChan, liveNode, "indexes", loggedInUser, dbOwner, dbName, "") + // Send the indexes request to our job queue backend + indexes, err = com.LiveIndexes(liveNode, loggedInUser, dbOwner, dbName) if err != nil { jsonErr(w, err.Error(), http.StatusInternalServerError) - log.Println(err) - return - } - - // Decode the response - var resp com.LiveDBIndexesResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - jsonErr(w, err.Error(), http.StatusInternalServerError) - log.Println(err) - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - jsonErr(w, err.Error(), http.StatusInternalServerError) - return - } - if resp.Node == "" { - log.Printf("In API (Live) indexesHandler(). A node responded, but didn't identify itself.") return } - indexes = resp.Indexes } // Return the results @@ -955,7 +933,7 @@ func queryHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } @@ -1088,11 +1066,11 @@ func tablesHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } - // If it's a standard database, process it locally. Else send the query to our AMQP backend + // If it's a standard database, process it locally. Else send the query to our job queue backend var tables []string if !isLive { // Get Minio bucket and object id for the SQLite file @@ -1125,7 +1103,7 @@ func tablesHandler(w http.ResponseWriter, r *http.Request) { return } } else { - // Send the tables request to our AMQP backend + // Send the tables request to our job queue backend tables, err = com.LiveTables(liveNode, loggedInUser, dbOwner, dbName) if err != nil { jsonErr(w, err.Error(), http.StatusInternalServerError) @@ -1361,8 +1339,8 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) { log.Printf("API Server: Username '%s' uploaded LIVE database '%s/%s', bytes: %v", loggedInUser, com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), numBytes) - // Send a request to the AMQP backend to set up the database there, ready for querying - liveNode, err := com.LiveCreateDB(com.AmqpChan, dbOwner, dbName, objectID) + // Send a request to the job queue to set up the database + liveNode, err := com.LiveCreateDB(dbOwner, dbName, objectID) if err != nil { log.Println(err) jsonErr(w, err.Error(), http.StatusInternalServerError) @@ -1448,11 +1426,11 @@ func viewsHandler(w http.ResponseWriter, r *http.Request) { // If a live database has been uploaded but doesn't have a live node handling its requests, then error out as this // should never happen if isLive && liveNode == "" { - jsonErr(w, "No AMQP node available for request", http.StatusInternalServerError) + jsonErr(w, "No job queue node available for request", http.StatusInternalServerError) return } - // If it's a standard database, process it locally. Else send the query to our AMQP backend + // If it's a standard database, process it locally. Else send the query to our job queue backend var views []string if !isLive { // Get Minio bucket and object id for the SQLite file @@ -1485,7 +1463,7 @@ func viewsHandler(w http.ResponseWriter, r *http.Request) { return } } else { - // Send the views request to our AMQP backend + // Send the views request to our job queue backend views, err = com.LiveViews(liveNode, loggedInUser, dbOwner, dbName) if err != nil { jsonErr(w, err.Error(), http.StatusInternalServerError) diff --git a/api/main.go b/api/main.go index a2bdc52f1..3d681e61f 100644 --- a/api/main.go +++ b/api/main.go @@ -46,13 +46,16 @@ func main() { log.Fatalf("Configuration file problem: '%s'", err) } + // Set the node name used in various logging strings + com.Conf.Live.Nodename = "API server" + // Open the request log for writing reqLog, err = os.OpenFile(com.Conf.Api.RequestLog, os.O_CREATE|os.O_APPEND|os.O_WRONLY|os.O_SYNC, 0750) if err != nil { log.Fatalf("Error when opening request log: %s", err) } defer reqLog.Close() - log.Printf("Request log opened: %s", com.Conf.Api.RequestLog) + log.Printf("%s: request log opened: %s", com.Conf.Live.Nodename, com.Conf.Api.RequestLog) // Parse our template files tmpl = template.Must(template.New("templates").Delims("[[", "]]").ParseGlob( @@ -70,9 +73,8 @@ func main() { log.Fatal(err) } - // Connect to MQ server - com.Conf.Live.Nodename = "API server" - com.AmqpChan, err = com.ConnectMQ() + // Connect to job queue server + err = com.ConnectQueue() if err != nil { log.Fatal(err) } @@ -95,6 +97,13 @@ func main() { log.Fatal(err) } + // Start background goroutines to handle job queue responses + com.ResponseWaiters = com.NewResponseReceiver() + com.CheckResponsesQueue = make(chan struct{}) + com.SubmitterInstance = com.RandomString(3) + go com.ResponseQueueCheck() + go com.ResponseQueueListen() + // Load our self signed CA chain ourCAPool = x509.NewCertPool() certFile, err := os.ReadFile(com.Conf.DB4S.CAChain) @@ -136,11 +145,10 @@ func main() { // Load our self signed CA Cert chain, check client certificates if given, and set TLS1.2 as minimum newTLSConfig := &tls.Config{ - ClientAuth: tls.VerifyClientCertIfGiven, - ClientCAs: ourCAPool, - MinVersion: tls.VersionTLS12, - PreferServerCipherSuites: true, - RootCAs: ourCAPool, + ClientAuth: tls.VerifyClientCertIfGiven, + ClientCAs: ourCAPool, + MinVersion: tls.VersionTLS12, + RootCAs: ourCAPool, } srv := &http.Server{ Addr: com.Conf.Api.BindAddress, @@ -153,7 +161,7 @@ func main() { server = fmt.Sprintf("https://%s", com.Conf.Api.ServerName) // Start API server - log.Printf("API server starting on %s", server) + log.Printf("%s: listening on %s", com.Conf.Live.Nodename, server) err = srv.ListenAndServeTLS(com.Conf.Api.Certificate, com.Conf.Api.CertificateKey) // Shut down nicely diff --git a/common/config.go b/common/config.go index e2409112a..e098c9168 100644 --- a/common/config.go +++ b/common/config.go @@ -2,13 +2,16 @@ package common import ( "crypto/tls" + "errors" "fmt" + "io/fs" "log" "os" "path/filepath" "strconv" "github.com/BurntSushi/toml" + "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/mitchellh/go-homedir" ) @@ -19,26 +22,30 @@ var ( // PostgreSQL configuration info pgConfig *pgxpool.Config + + // Configuration info for the PostgreSQL job queue + listenConfig *pgx.ConnConfig ) // ReadConfig reads the server configuration file. -func ReadConfig() error { +func ReadConfig() (err error) { // Override config file location via environment variables - var err error configFile := os.Getenv("CONFIG_FILE") if configFile == "" { // TODO: Might be a good idea to add permission checks of the dir & conf file, to ensure they're not // world readable. Similar in concept to what ssh does for its config files. - userHome, err := homedir.Dir() + var userHome string + userHome, err = homedir.Dir() if err != nil { log.Printf("User home directory couldn't be determined: '%s'", err) - return err + return } configFile = filepath.Join(userHome, ".dbhub", "config.toml") } // Reads the server configuration from disk - if _, err := toml.DecodeFile(configFile, &Conf); err != nil { + _, err = toml.DecodeFile(configFile, &Conf) + if err != nil { return fmt.Errorf("Config file couldn't be parsed: %s", err) } @@ -166,14 +173,18 @@ func ReadConfig() error { } // Check cache directory exists - if _, err := os.Stat(Conf.DiskCache.Directory); os.IsNotExist(err) { + _, err = os.Stat(Conf.DiskCache.Directory) + if errors.Is(err, fs.ErrNotExist) { if os.MkdirAll(Conf.DiskCache.Directory, 0775) != nil { - log.Fatal(err) + return } } - // Set the PostgreSQL configuration values + // Set the main PostgreSQL database configuration values pgConfig, err = pgxpool.ParseConfig(fmt.Sprintf("host=%s port=%d user= %s password = %s dbname=%s pool_max_conns=%d connect_timeout=10", Conf.Pg.Server, uint16(Conf.Pg.Port), Conf.Pg.Username, Conf.Pg.Password, Conf.Pg.Database, Conf.Pg.NumConnections)) + if err != nil { + return + } clientTLSConfig := tls.Config{} if Conf.Environment.Environment == "production" { clientTLSConfig.ServerName = Conf.Pg.Server @@ -187,6 +198,24 @@ func ReadConfig() error { pgConfig.ConnConfig.TLSConfig = nil } + // Create the connection string for the dedicated PostgreSQL notification connection + listenConfig, err = pgx.ParseConfig(fmt.Sprintf("host=%s port=%d user= %s password = %s dbname=%s connect_timeout=10", Conf.Pg.Server, uint16(Conf.Pg.Port), Conf.Pg.Username, Conf.Pg.Password, Conf.Pg.Database)) + if err != nil { + return + } + listenTLSConfig := tls.Config{} + if Conf.Environment.Environment == "production" { + listenTLSConfig.ServerName = Conf.Pg.Server + listenTLSConfig.InsecureSkipVerify = false + } else { + listenTLSConfig.InsecureSkipVerify = true + } + if Conf.Pg.SSL { + listenConfig.TLSConfig = &listenTLSConfig + } else { + listenConfig.TLSConfig = nil + } + // Environment variable override for non-production logged-in user tempString = os.Getenv("DBHUB_USERNAME") if tempString != "" { @@ -194,5 +223,5 @@ func ReadConfig() error { } // The configuration file seems good - return nil + return } diff --git a/common/config_types.go b/common/config_types.go index d46245bdf..d68a51156 100644 --- a/common/config_types.go +++ b/common/config_types.go @@ -14,7 +14,6 @@ type TomlConfig struct { Live LiveInfo Memcache MemcacheInfo Minio MinioInfo - MQ MQInfo Pg PGInfo Sign SigningInfo UserMgmt UserMgmtInfo @@ -93,16 +92,6 @@ type MinioInfo struct { Server string } -// MQInfo contains the AMQP backend connection configuration info -type MQInfo struct { - CertFile string `toml:"cert_file"` - KeyFile string `toml:"key_file"` - Password string `toml:"password"` - Port int `toml:"port"` - Server string `toml:"server"` - Username string `toml:"username"` -} - // PGInfo contains the PostgreSQL connection parameters type PGInfo struct { Database string diff --git a/common/cypress.go b/common/cypress.go index 153750d1f..53eb81b52 100644 --- a/common/cypress.go +++ b/common/cypress.go @@ -84,10 +84,10 @@ func CypressSeed(w http.ResponseWriter, r *http.Request) { return } - // Send the live database file to our AMQP backend for setup + // Send the live database file to our job queue backend for setup dbOwner := "default" dbName := "Join Testing with index.sqlite" - liveNode, err := LiveCreateDB(AmqpChan, dbOwner, dbName, objectID) + liveNode, err := LiveCreateDB(dbOwner, dbName, objectID) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/common/errorcodes.go b/common/errorcodes.go index 5069a531e..652ad18e1 100644 --- a/common/errorcodes.go +++ b/common/errorcodes.go @@ -1,21 +1,21 @@ package common -// A set of error codes returned by the AMQP back end. We use these so we can -// change the user facing text as desired without having to worry about -// potentially breaking the AMQP communication interface +// A set of error codes returned by our job queue back end. We use these so we can +// change the user facing text as desired without having to worry about potentially +// breaking the job queue communication interface -type AMQPErrorCode int +type JobQueueErrorCode int const ( - AMQPNoError AMQPErrorCode = iota - AMQPRequestedTableNotPresent + JobQueueNoError JobQueueErrorCode = iota + JobQueueRequestedTableNotPresent ) -func AMQPErrorString(errCode AMQPErrorCode) string { +func JobQueueErrorString(errCode JobQueueErrorCode) string { switch errCode { - case AMQPNoError: + case JobQueueNoError: return "no error" - case AMQPRequestedTableNotPresent: + case JobQueueRequestedTableNotPresent: return "Provided table or view name doesn't exist in this database" default: return "unknown error" diff --git a/common/licences.go b/common/licences.go index 77e63977f..e7bb20ba7 100644 --- a/common/licences.go +++ b/common/licences.go @@ -85,6 +85,6 @@ func AddDefaultLicences() (err error) { return err } } - log.Println("Default licences added") + log.Printf("%s: default licences added", Conf.Live.Nodename) return nil } diff --git a/common/live.go b/common/live.go index 125c94a7b..84e9afbbd 100644 --- a/common/live.go +++ b/common/live.go @@ -2,16 +2,20 @@ package common import ( "context" - "crypto/tls" "encoding/json" "errors" "fmt" + "io/fs" "log" + "os" + "path/filepath" "sort" + "strings" "time" sqlite "github.com/gwenn/gosqlite" - amqp "github.com/rabbitmq/amqp091-go" + "github.com/jackc/pgx/v5" + pgpool "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -19,321 +23,251 @@ const ( ) var ( - // AmqpChan is the AMQP channel handle we use for communication with our AMQP backend - AmqpChan *amqp.Channel + // JobListenConn is the PG server connection used for receiving PG notifications + JobListenConn *pgx.Conn - // AmqpDebug controls whether to output - via Log.Print*() functions - useful messages during processing. Mostly - // useful for development / debugging purposes - AmqpDebug = 1 -) + // JobQueueConn is the PG server connection used for submitting and retrieving jobs + JobQueueConn *pgpool.Pool -// CloseMQChannel closes an open AMQP channel -func CloseMQChannel(channel *amqp.Channel) (err error) { - err = channel.Close() - return -} - -// CloseMQConnection closes an open AMQP connection -func CloseMQConnection(connection *amqp.Connection) (err error) { - err = connection.Close() - return -} + // JobQueueDebug tells the daemons whether or not to output debug messages while running job queue code + // Mostly useful for development / debugging purposes. 0 means no debug messages, higher values means more verbosity + JobQueueDebug = 0 +) -// ConnectMQ creates a connection to the backend MQ server -func ConnectMQ() (channel *amqp.Channel, err error) { - var conn *amqp.Connection - if Conf.Environment.Environment == "production" { - // If certificate/key files have been provided, then we can use mutual TLS (mTLS) - if Conf.MQ.CertFile != "" && Conf.MQ.KeyFile != "" { - var cert tls.Certificate - cert, err = tls.LoadX509KeyPair(Conf.MQ.CertFile, Conf.MQ.KeyFile) - if err != nil { - return - } - cfg := &tls.Config{Certificates: []tls.Certificate{cert}} - conn, err = amqp.DialTLS(fmt.Sprintf("amqps://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port), cfg) - if err != nil { - return - } - log.Printf("%s connected to AMQP server using mutual TLS (mTLS): %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) - } else { - // Fallback to just verifying the server certs for TLS. This is needed by the DB4S end point, as it - // uses certs from our own CA, so mTLS won't easily work with it. - conn, err = amqp.Dial(fmt.Sprintf("amqps://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port)) - if err != nil { - return - } - log.Printf("%s connected to AMQP server with server-only TLS: %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) - } - } else { - // Everywhere else (eg docker container) doesn't *have* to use TLS - conn, err = amqp.Dial(fmt.Sprintf("amqp://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port)) - if err != nil { - return - } - log.Printf("%s connected to AMQP server without encryption: %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) +// ConnectQueue creates the connections to the backend queue server +func ConnectQueue() (err error) { + // Connect to PostgreSQL based queue server + // Note: JobListenConn uses a dedicated, non-pooled connection to the job queue database, while JobQueueConn uses + // a standard database connection pool + JobListenConn, err = pgx.ConnectConfig(context.Background(), listenConfig) + if err != nil { + return fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) + } + JobQueueConn, err = pgpool.New(context.Background(), pgConfig.ConnString()) + if err != nil { + return fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) } - - channel, err = conn.Channel() return } -// LiveBackup asks the AMQP backend to store the given database back into Minio +// LiveBackup asks the job queue backend to store the given database back into Minio func LiveBackup(liveNode, loggedInUser, dbOwner, dbName string) (err error) { - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "backup", loggedInUser, dbOwner, dbName, "") + // Send the backup request to our job queue backend + var resp JobResponseDBError + err = JobSubmit(&resp, liveNode, "backup", loggedInUser, dbOwner, dbName, "") if err != nil { return } - // Decode the response - var resp LiveDBErrorResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - return - } + log.Printf("%s: node which handled the database backup request: %s", Conf.Live.Nodename, liveNode) - // If the backup failed, then provide the error message to the user - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'backup' request, but didn't identify itself.") - return + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database backup on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) } return } -// LiveColumns requests the AMQP backend to return a list of all columns of the given table +// LiveColumns requests the job queue backend to return a list of all columns of the given table func LiveColumns(liveNode, loggedInUser, dbOwner, dbName, table string) (columns []sqlite.Column, pk []string, err error) { - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "columns", loggedInUser, dbOwner, dbName, table) + // Send the column list request to our job queue backend + var resp JobResponseDBColumns + err = JobSubmit(&resp, liveNode, "columns", loggedInUser, dbOwner, dbName, table) if err != nil { return } - // Decode the response - var resp LiveDBColumnsResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'columns' request, but didn't identify itself.") - return - } + // Return the requested data columns = resp.Columns pk = resp.PkColumns + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the column list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } return } -// LiveCreateDB requests the AMQP backend create a new live SQLite database -func LiveCreateDB(channel *amqp.Channel, dbOwner, dbName, objectID string) (liveNode string, err error) { - // Send the database setup request to our AMQP backend - var rawResponse []byte - rawResponse, err = MQRequest(channel, "create_queue", "createdb", "", dbOwner, dbName, objectID) +// LiveCreateDB requests the job queue backend create a new live SQLite database +func LiveCreateDB(dbOwner, dbName, objectID string) (liveNode string, err error) { + // Send the database setup request to our job queue backend + var resp JobResponseDBCreate + err = JobSubmit(&resp, "any", "createdb", "", dbOwner, dbName, objectID) if err != nil { return } - // Decode the response - var resp LiveDBResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - log.Println(err) - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'create' request, but didn't identify itself.") - return - } - if resp.Result != "success" { - err = errors.New(fmt.Sprintf("LIVE database (%s/%s) creation apparently didn't fail, but the response didn't include a success message", - dbOwner, dbName)) - return + // Return the name of the node which has the database + liveNode = resp.NodeName + + log.Printf("%s: node which handled the database creation request: %s", Conf.Live.Nodename, liveNode) + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database creation on '%s': '%v'", Conf.Live.Nodename, resp.NodeName, resp.Err) } - liveNode = resp.Node return } -// LiveDelete asks our AMQP backend to delete a database +// LiveDelete asks our job queue backend to delete a database func LiveDelete(liveNode, loggedInUser, dbOwner, dbName string) (err error) { - // Delete the database from our AMQP backend - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "delete", loggedInUser, dbOwner, dbName, "") + // Send the database setup request to our job queue backend + var resp JobResponseDBError + err = JobSubmit(&resp, liveNode, "delete", loggedInUser, dbOwner, dbName, "") if err != nil { - log.Println(err) return } - // Decode the response - var resp LiveDBErrorResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - log.Println(err) - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'delete' request, but didn't identify itself.") - return + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database deletion on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) } return } -// LiveExecute asks our AMQP backend to execute a SQL statement on a database +// LiveExecute asks our job queue backend to execute a SQL statement on a database func LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql string) (rowsChanged int, err error) { - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "execute", loggedInUser, dbOwner, dbName, sql) + // Send the execute request to our job queue backend + var resp JobResponseDBExecute + err = JobSubmit(&resp, liveNode, "execute", loggedInUser, dbOwner, dbName, sql) if err != nil { return } - // Decode the response - var resp LiveDBExecuteResponse - err = json.Unmarshal(rawResponse, &resp) + // Return the number of rows changed by the execution run + rowsChanged = resp.RowsChanged + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + if !strings.HasPrefix(err.Error(), "don't use exec with") { + log.Printf("%s: an error was returned when retrieving the execution result for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } + } + return +} + +// LiveIndexes asks our job queue backend to provide the list of indexes in a database +func LiveIndexes(liveNode, loggedInUser, dbOwner, dbName string) (indexes []APIJSONIndex, err error) { + // Send the index request to our job queue backend + var resp JobResponseDBIndexes + err = JobSubmit(&resp, liveNode, "indexes", loggedInUser, dbOwner, dbName, "") if err != nil { - log.Println(err) return } - // If the SQL execution failed, then provide the error message to the user - if resp.Error != "" { - err = errors.New(resp.Error) - return + // Return the index list for the live database + indexes = resp.Indexes + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the index list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) } - rowsChanged = resp.RowsChanged return } // LiveQuery sends a SQLite query to a live database on its hosting node -func LiveQuery(nodeName, requestingUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) { - // Send the query request to our AMQP backend - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, nodeName, "query", requestingUser, dbOwner, dbName, query) +func LiveQuery(liveNode, loggedInUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) { + // Send the query to our job queue backend + var resp JobResponseDBQuery + err = JobSubmit(&resp, liveNode, "query", loggedInUser, dbOwner, dbName, query) if err != nil { return } - // Decode the response - var resp LiveDBQueryResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - log.Println(err) - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } + // Return the query response rows = resp.Results + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the query response for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } return } -// LiveRowData asks our AMQP backend to send us the SQLite table data for a given range of rows -func LiveRowData(liveNode, loggedInUser, dbOwner, dbName string, reqData LiveDBRowsRequest) (rowData SQLiteRecordSet, err error) { - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqData) +// LiveRowData asks our job queue backend to send us the SQLite table data for a given range of rows +func LiveRowData(liveNode, loggedInUser, dbOwner, dbName string, reqData JobRequestRows) (rowData SQLiteRecordSet, err error) { + // Serialise the row data request to JSON + // NOTE - This actually causes the serialised field to be stored in PG as base64 instead. Not sure why, but we can work with it. + reqJSON, err := json.Marshal(reqData) if err != nil { log.Println(err) return } - // Decode the response - var resp LiveDBRowsResponse - err = json.Unmarshal(rawResponse, &resp) + // Send the row data request to our job queue backend + var resp JobResponseDBRows + err = JobSubmit(&resp, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqJSON) if err != nil { - log.Println(err) - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - log.Println(err) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'rowdata' request, but didn't identify itself.") return } + + // Return the row data for the requested table rowData = resp.RowData + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the row data for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } return } -// LiveSize asks our AMQP backend for the file size of a database +// LiveSize asks our job queue backend for the file size of a database func LiveSize(liveNode, loggedInUser, dbOwner, dbName string) (size int64, err error) { - // Send the size request to our AMQP backend - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "size", loggedInUser, dbOwner, dbName, "") + // Send the size request to our job queue backend + var resp JobResponseDBSize + err = JobSubmit(&resp, liveNode, "size", loggedInUser, dbOwner, dbName, "") if err != nil { return } - // Decode the response - var resp LiveDBSizeResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'size' request, but didn't identify itself.") - return - } + // Return the size of the live database size = resp.Size + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when checking the on disk database size for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } return } -// LiveTables asks our AMQP backend to provide the list of tables (not including views!) in a database +// LiveTables asks our job queue backend to provide the list of tables (not including views!) in a database func LiveTables(liveNode, loggedInUser, dbOwner, dbName string) (tables []string, err error) { - // Send the tables request to our AMQP backend - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "tables", loggedInUser, dbOwner, dbName, "") + // Send the tables request to our job queue backend + var resp JobResponseDBTables + err = JobSubmit(&resp, liveNode, "tables", loggedInUser, dbOwner, dbName, "") if err != nil { return } - // Decode the response - var resp LiveDBTablesResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'tables' request, but didn't identify itself.") - return - } + // Return the table list for the live database tables = resp.Tables + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the table list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } return } -// LiveTablesAndViews asks our AMQP backend to provide the list of tables and views in a database +// LiveTablesAndViews asks our job queue backend to provide the list of tables and views in a database func LiveTablesAndViews(liveNode, loggedInUser, dbOwner, dbName string) (list []string, err error) { - // Send the tables request to our AMQP backend + // Send the tables request to our job queue backend list, err = LiveTables(liveNode, loggedInUser, dbOwner, dbName) if err != nil { return } - // Send the tables request to our AMQP backend + // Send the tables request to our job queue backend var vw []string vw, err = LiveViews(liveNode, loggedInUser, dbOwner, dbName) if err != nil { @@ -346,180 +280,92 @@ func LiveTablesAndViews(liveNode, loggedInUser, dbOwner, dbName string) (list [] return } -// LiveViews asks our AMQP backend to provide the list of views (not including tables!) in a database +// LiveViews asks our job queue backend to provide the list of views (not including tables!) in a database func LiveViews(liveNode, loggedInUser, dbOwner, dbName string) (views []string, err error) { - var rawResponse []byte - rawResponse, err = MQRequest(AmqpChan, liveNode, "views", loggedInUser, dbOwner, dbName, "") + // Send the views request to our job queue backend + var resp JobResponseDBViews + err = JobSubmit(&resp, liveNode, "views", loggedInUser, dbOwner, dbName, "") if err != nil { return } - // Decode the response - var resp LiveDBViewsResponse - err = json.Unmarshal(rawResponse, &resp) - if err != nil { - return - } - if resp.Error != "" { - err = errors.New(resp.Error) - return - } - if resp.Node == "" { - log.Println("A node responded to a 'views' request, but didn't identify itself.") - return - } + // Return the view list for the live database views = resp.Views - return -} - -// MQResponse sends an AMQP response back to its requester -func MQResponse(requestType string, msg amqp.Delivery, channel *amqp.Channel, nodeName string, responseData interface{}) (err error) { - var z []byte - z, err = json.Marshal(responseData) - if err != nil { - log.Println(err) - // It's super unlikely we can safely return here without ack-ing the message. So as something has gone - // wrong with json.Marshall() we'd better just attempt passing back info about that error message instead (!) - z = []byte(fmt.Sprintf(`{"node":"%s","error":"%s"}`, nodeName, err.Error())) // This is a LiveDBErrorResponse structure - } - - // Send the message - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) - defer cancel() - err = channel.PublishWithContext(ctx, "", msg.ReplyTo, false, false, - amqp.Publishing{ - ContentType: "text/json", - CorrelationId: msg.CorrelationId, - Body: z, - }) - if err != nil { - log.Println(err) - } - - // Acknowledge the request, so it doesn't stick around in the queue - err = msg.Ack(false) - if err != nil { - log.Println(err) - } - if AmqpDebug > 0 { - log.Printf("[%s] Live node '%s' responded with ACK to message with correlationID: '%s', msg.ReplyTo: '%s'", requestType, nodeName, msg.CorrelationId, msg.ReplyTo) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the view list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) } return } -// MQCreateDBQueue creates a queue on the MQ server for "create database" messages -func MQCreateDBQueue(channel *amqp.Channel) (queue amqp.Queue, err error) { - queue, err = channel.QueueDeclare("create_queue", true, false, false, false, nil) - if err != nil { - return - } +// RemoveLiveDB deletes a live database from the local node. For example, when the user deletes it from +// their account. +// Be aware, it leaves the database owners directory in place, to avoid any potential race condition of +// trying to delete that directory while other databases in their account are being worked with +func RemoveLiveDB(dbOwner, dbName string) (err error) { + // Get the path to the database file, and it's containing directory + dbDir := filepath.Join(Conf.Live.StorageDir, dbOwner, dbName) + dbPath := filepath.Join(dbDir, "live.sqlite") + if _, err = os.Stat(dbPath); err != nil { + if errors.Is(err, fs.ErrNotExist) { + if JobQueueDebug > 0 { + log.Printf("%s: database file '%s/%s' was supposed to get deleted here, but was missing from "+ + "filesystem path: '%s'", Conf.Live.Nodename, dbOwner, dbName, dbPath) + } + return + } - // FIXME: Re-read the docs for this, and work out if this is needed - err = channel.Qos(1, 0, false) - if err != nil { + // Something wrong with the database file + log.Println(err) return } - return -} -// MQCreateQueryQueue creates a queue on the MQ server for sending database queries to -func MQCreateQueryQueue(channel *amqp.Channel, nodeName string) (queue amqp.Queue, err error) { - queue, err = channel.QueueDeclare(nodeName, false, false, false, false, nil) + // Delete the "live.sqlite" file + // NOTE: If this seems to leave wal or other files hanging around in actual production use, we could + // instead use filepath.RemoveAll(dbDir). That should kill the containing directory and + // all files within, thus not leave anything hanging around + err = os.Remove(dbPath) if err != nil { + log.Println(err) return } - // FIXME: Re-read the docs for this, and work out if this is needed - err = channel.Qos(0, 0, false) + // Remove the containing directory + err = os.Remove(dbDir) if err != nil { + log.Println(err) return } - return -} -// MQCreateResponse sends a success/failure response back -func MQCreateResponse(msg amqp.Delivery, channel *amqp.Channel, nodeName, result string) (err error) { - // Construct the response. It's such a simple string we just create it directly instead of using json.Marshall() - resp := fmt.Sprintf(`{"node":"%s","dbowner":"","dbname":"","result":"%s","error":""}`, nodeName, result) - - // Send the message - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) - defer cancel() - err = channel.PublishWithContext(ctx, "", msg.ReplyTo, false, false, - amqp.Publishing{ - ContentType: "text/json", - CorrelationId: msg.CorrelationId, - Body: []byte(resp), - }) - if err != nil { - log.Println(err) - } - msg.Ack(false) - if AmqpDebug > 0 { - log.Printf("[CREATE] Live node '%s' responded with ACK to message with correlationID: '%s', msg.ReplyTo: '%s'", nodeName, msg.CorrelationId, msg.ReplyTo) + if JobQueueDebug > 0 { + log.Printf("%s: database file '%s/%s' removed from filesystem path: '%s'", Conf.Live.Nodename, dbOwner, + dbName, dbPath) } return } -// MQRequest is the main function used for sending requests to our AMQP backend -func MQRequest(channel *amqp.Channel, queue, operation, requestingUser, dbOwner, dbName string, data interface{}) (result []byte, err error) { - // Create a temporary AMQP queue for receiving the response - var q amqp.Queue - q, err = channel.QueueDeclare("", false, false, true, false, nil) - if err != nil { - return - } +// WaitForResponse waits for the job queue server to provide a response for a given job id +func WaitForResponse[T any](jobID int, resp *T) (err error) { + // Add the response receiver + responseChan := make(chan ResponseInfo) + ResponseWaiters.AddReceiver(jobID, &responseChan) - // Construct the request - bar := LiveDBRequest{ - Operation: operation, - DBOwner: dbOwner, - DBName: dbName, - Data: data, - RequestingUser: requestingUser, - } - var z []byte - z, err = json.Marshal(bar) - if err != nil { - log.Println(err) - return - } + // Wait for a response + response := <-responseChan - // Send the request via AMQP - ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) - defer cancel() - corrID := RandomString(32) - err = channel.PublishWithContext(ctx, "", queue, false, false, - amqp.Publishing{ - ContentType: "text/json", - CorrelationId: corrID, - ReplyTo: q.Name, - Body: z, - }) - if err != nil { - log.Println(err) - return - } + // Remove the response receiver + ResponseWaiters.RemoveReceiver(jobID) - // Start processing messages from the AMQP response queue - msgs, err := channel.Consume(q.Name, "", true, false, false, false, nil) - if err != nil { - return - } + // Update the response status to 'processed' (should be fine done async) + go ResponseComplete(response.responseID) - // Wait for, then extract the response. Without json unmarshalling it yet - for d := range msgs { - if corrID == d.CorrelationId { - result = d.Body - break - } - } - - // Delete the temporary queue - _, err = channel.QueueDelete(q.Name, false, false, false) + // Unmarshall the response + err = json.Unmarshal([]byte(response.payload), resp) if err != nil { - log.Println(err) + err = fmt.Errorf("couldn't decode response payload: '%s'", err) + log.Printf("%s: %s", Conf.Live.Nodename, err) } return } diff --git a/common/live_types.go b/common/live_types.go index bc91d3d88..df5a65ee8 100644 --- a/common/live_types.go +++ b/common/live_types.go @@ -1,99 +1,127 @@ package common import ( + "sync" + sqlite "github.com/gwenn/gosqlite" ) -// LiveDBColumnsResponse holds the fields used for receiving column list responses from our AMQP backend -type LiveDBColumnsResponse struct { - Node string `json:"node"` - Columns []sqlite.Column `json:"columns"` - PkColumns []string `json:"pkColuns"` - Error string `json:"error"` - ErrCode AMQPErrorCode `json:"error_code"` +// JobRequest holds the fields used for sending requests to our job request backend +type JobRequest struct { + Operation string `json:"operation"` + DBOwner string `json:"dbowner"` + DBName string `json:"dbname"` + Data interface{} `json:"data,omitempty"` + RequestingUser string `json:"requesting_user"` } -// LiveDBErrorResponse holds just the node name and any error message used in responses by our AMQP backend -// It's useful for error message, and other responses where no other fields are needed -type LiveDBErrorResponse struct { - Node string `json:"node"` - Error string `json:"error"` +// JobRequestRows holds the data used when making a rows request to our job queue backend +type JobRequestRows struct { + DbTable string `json:"db_table"` + SortCol string `json:"sort_col"` + SortDir string `json:"sort_dir"` + CommitID string `json:"commit_id"` + RowOffset int `json:"row_offset"` + MaxRows int `json:"max_rows"` } -// LiveDBExecuteResponse returns the number of rows changed by an Execute() call -type LiveDBExecuteResponse struct { - Node string `json:"node"` - RowsChanged int `json:"rows_changed"` - Error string `json:"error"` +// JobResponseDBColumns holds the fields used for receiving column list responses from our job queue backend +type JobResponseDBColumns struct { + Columns []sqlite.Column `json:"columns"` + Err string `json:"error"` + ErrCode JobQueueErrorCode `json:"error_code"` + PkColumns []string `json:"pkColumns"` } -// LiveDBIndexesResponse holds the fields used for receiving index list responses from our AMQP backend -type LiveDBIndexesResponse struct { - Node string `json:"node"` - Indexes []APIJSONIndex `json:"indexes"` - Error string `json:"error"` +// JobResponseDBCreate holds the fields used for receiving database creation responses from our job queue backend +type JobResponseDBCreate struct { + Err string `json:"error"` + NodeName string `json:"node_name"` } -// LiveDBQueryResponse holds the fields used for receiving query responses from our AMQP backend -type LiveDBQueryResponse struct { - Node string `json:"node"` - Results SQLiteRecordSet `json:"results"` - Error string `json:"error"` +// JobResponseDBError holds the structure used when our job queue backend only needs to response with an error field (empty or not) +type JobResponseDBError struct { + Err string `json:"error"` } -// LiveDBRequest holds the fields used for sending requests to our AMQP backend -type LiveDBRequest struct { - Operation string `json:"operation"` - DBOwner string `json:"dbowner"` - DBName string `json:"dbname"` - Data interface{} `json:"data,omitempty"` - RequestingUser string `json:"requesting_user"` +// JobResponseDBExecute holds the fields used for receiving the database execute response from our job queue backend +type JobResponseDBExecute struct { + Err string `json:"error"` + RowsChanged int `json:"rows_changed"` } -// LiveDBResponse holds the fields used for receiving (non-query) responses from our AMQP backend -type LiveDBResponse struct { - Node string `json:"node"` - Result string `json:"result"` - Error string `json:"error"` +// JobResponseDBIndexes holds the fields used for receiving the database index list from our job queue backend +type JobResponseDBIndexes struct { + Err string `json:"error"` + Indexes []APIJSONIndex `json:"indexes"` } -// LiveDBRowsRequest holds the data used when making an AMQP rows request -type LiveDBRowsRequest struct { - DbTable string `json:"db_table"` - SortCol string `json:"sort_col"` - SortDir string `json:"sort_dir"` - CommitID string `json:"commit_id"` - RowOffset int `json:"row_offset"` - MaxRows int `json:"max_rows"` +// JobResponseDBQuery holds the fields used for receiving database query results from our job queue backend +type JobResponseDBQuery struct { + Err string `json:"error"` + Results SQLiteRecordSet `json:"results"` } -// LiveDBRowsResponse holds the fields used for receiving database page row responses from our AMQP backend -type LiveDBRowsResponse struct { - Node string `json:"node"` +// JobResponseDBRows holds the fields used for receiving table row data from our job queue backend +type JobResponseDBRows struct { DatabaseSize int64 `json:"database_size"` DefaultTable string `json:"default_table"` + Err string `json:"error"` RowData SQLiteRecordSet `json:"row_data"` Tables []string `json:"tables"` - Error string `json:"error"` } -// LiveDBSizeResponse holds the fields used for receiving database size responses from our AMQP backend -type LiveDBSizeResponse struct { - Node string `json:"node"` - Size int64 `json:"size"` - Error string `json:"error"` +// JobResponseDBSize holds the fields used for receiving database size responses from our job queue backend +type JobResponseDBSize struct { + Err string `json:"error"` + Size int64 `json:"size"` } -// LiveDBTablesResponse holds the fields used for receiving table list responses from our AMQP backend -type LiveDBTablesResponse struct { - Node string `json:"node"` +// JobResponseDBTables holds the fields used for receiving the database table list from our job queue backend +type JobResponseDBTables struct { + Err string `json:"error"` Tables []string `json:"tables"` - Error string `json:"error"` } -// LiveDBViewsResponse holds the fields used for receiving view list responses from our AMQP backend -type LiveDBViewsResponse struct { - Node string `json:"node"` +// JobResponseDBViews holds the fields used for receiving the database views list from our job queue backend +type JobResponseDBViews struct { + Err string `json:"error"` Views []string `json:"views"` - Error string `json:"error"` +} + +// ResponseInfo holds job queue responses. Most of the useful info is json encoded in the payload field +type ResponseInfo struct { + jobID int + responseID int + payload string +} + +// ResponseReceivers is a simple structure used for matching up job queue responses to the caller who submitted the job +type ResponseReceivers struct { + sync.Mutex + receivers map[int]*chan ResponseInfo +} + +// NewResponseReceiver is the constructor function for correcting creating new ResponseReceivers structures +func NewResponseReceiver() *ResponseReceivers { + z := ResponseReceivers{ + Mutex: sync.Mutex{}, + receivers: nil, + } + z.receivers = make(map[int]*chan ResponseInfo) + return &z +} + +// AddReceiver adds a new response receiver +func (r *ResponseReceivers) AddReceiver(jobID int, newReceiver *chan ResponseInfo) { + r.Lock() + r.receivers[jobID] = newReceiver + r.Unlock() +} + +// RemoveReceiver removes a response receiver (generally after it has received the response it was waiting for) +func (r *ResponseReceivers) RemoveReceiver(jobID int) { + r.Lock() + delete(r.receivers, jobID) + r.Unlock() } diff --git a/common/memcache.go b/common/memcache.go index bf1834d76..c86dce210 100644 --- a/common/memcache.go +++ b/common/memcache.go @@ -58,7 +58,7 @@ func ConnectCache() error { } // Log successful connection message for Memcached - log.Printf("Connected to Memcached: %v", Conf.Memcache.Server) + log.Printf("%v: connected to Memcached: %v", Conf.Live.Nodename, Conf.Memcache.Server) return nil } diff --git a/common/minio.go b/common/minio.go index d30a66a86..d3b2e35e2 100644 --- a/common/minio.go +++ b/common/minio.go @@ -26,7 +26,7 @@ func ConnectMinio() (err error) { } // Log Minio server end point - log.Printf("Minio server config ok. Address: %v", Conf.Minio.Server) + log.Printf("%v: minio server config ok. Address: %v", Conf.Live.Nodename, Conf.Minio.Server) return nil } @@ -82,9 +82,8 @@ func LiveRetrieveDatabaseMinio(baseDir, dbOwner, dbName, objectID string) (dbPat return } - if AmqpDebug > 0 { - log.Printf("Live node '%s': Database file '%s/%s' written to filesystem at: '%s'", - Conf.Live.Nodename, dbOwner, dbName, dbPath) + if JobQueueDebug > 0 { + log.Printf("%s: database file '%s/%s' written to filesystem at: '%s'", Conf.Live.Nodename, dbOwner, dbName, dbPath) } return } @@ -132,12 +131,12 @@ func LiveStoreDatabaseMinio(db *os.File, dbOwner, dbName string, dbSize int64) ( // Sanity check. Make sure the # of bytes written is equal to the size of the database we were given if dbSize != numBytes { - err = errors.New(fmt.Sprintf("Something went wrong storing the database file. dbSize = %d, numBytes = %d", - dbSize, numBytes)) + err = fmt.Errorf("Something went wrong storing the database file. dbSize = %d, numBytes = %d", + dbSize, numBytes) return } - if AmqpDebug > 0 { + if JobQueueDebug > 0 { log.Printf("Added Minio LIVE database object '%s/%s', using bucket '%s' and id '%s'", dbOwner, dbName, bkt, minioObjectID) } return @@ -150,9 +149,9 @@ func MinioDeleteDatabase(source, dbOwner, dbName, bucket, id string) (err error) return } - if AmqpDebug > 0 { - log.Printf("[DELETE] '%s' removed Minio database object '%s/%s', using bucket '%s' and id '%s'", - source, dbOwner, dbName, bucket, id) + if JobQueueDebug > 0 { + log.Printf("%s: [DELETE] '%s' removed Minio database object '%s/%s', using bucket '%s' and id '%s'", + Conf.Live.Nodename, source, dbOwner, dbName, bucket, id) } return } diff --git a/common/postgresql.go b/common/postgresql.go index 0ca092608..cd1c90f34 100644 --- a/common/postgresql.go +++ b/common/postgresql.go @@ -51,7 +51,7 @@ func AddDefaultUser() error { } // Log addition of the default user - log.Println("Default user added") + log.Printf("%v: default user added", Conf.Live.Nodename) return nil } @@ -563,7 +563,7 @@ func ConnectPostgreSQL() (err error) { } // Log successful connection - log.Printf("Connected to PostgreSQL server: %v:%v", Conf.Pg.Server, uint16(Conf.Pg.Port)) + log.Printf("%v: connected to PostgreSQL server: %v:%v", Conf.Live.Nodename, Conf.Pg.Server, uint16(Conf.Pg.Port)) return nil } @@ -1087,14 +1087,14 @@ func DeleteDatabase(dbOwner, dbName string) error { AND db_name = $2` commandTag, err = tx.Exec(context.Background(), dbQuery, dbOwner, dbName, newName) if err != nil { - log.Printf("Deleting (forked) database entry failed for database '%s/%s': %v", - SanitiseLogString(dbOwner), SanitiseLogString(dbName), err) + log.Printf("%s: deleting (forked) database entry failed for database '%s/%s': %v", + Conf.Live.Nodename, SanitiseLogString(dbOwner), SanitiseLogString(dbName), err) return err } if numRows := commandTag.RowsAffected(); numRows != 1 { log.Printf( - "Wrong number of rows (%d) affected when deleting (forked) database '%s/%s'", numRows, - SanitiseLogString(dbOwner), SanitiseLogString(dbName)) + "%s: wrong number of rows (%d) affected when deleting (forked) database '%s/%s'", + Conf.Live.Nodename, numRows, SanitiseLogString(dbOwner), SanitiseLogString(dbName)) } // Commit the transaction @@ -1104,7 +1104,7 @@ func DeleteDatabase(dbOwner, dbName string) error { } // Log the database deletion - log.Printf("Database '%s/%s' deleted", SanitiseLogString(dbOwner), SanitiseLogString(dbName)) + log.Printf("%s: database '%s/%s' deleted", Conf.Live.Nodename, SanitiseLogString(dbOwner), SanitiseLogString(dbName)) return nil } @@ -1193,7 +1193,8 @@ func DeleteDatabase(dbOwner, dbName string) error { } // Log the database deletion - log.Printf("(Forked) database '%s/%s' deleted", SanitiseLogString(dbOwner), SanitiseLogString(dbName)) + log.Printf("%s: (forked) database '%s/%s' deleted", Conf.Live.Nodename, SanitiseLogString(dbOwner), + SanitiseLogString(dbName)) return nil } @@ -1204,6 +1205,7 @@ func DeleteLicence(userName, licenceName string) (err error) { if err != nil { return err } + // Set up an automatic transaction roll back if the function exits without committing defer tx.Rollback(context.Background()) @@ -1507,13 +1509,12 @@ func FlushViewCount() { } // Log the start of the loop - log.Printf("Periodic view count flushing loop started. %d second refresh.", - Conf.Memcache.ViewCountFlushDelay) + log.Printf("%s: periodic view count flushing loop started. %d second refresh.", Conf.Live.Nodename, Conf.Memcache.ViewCountFlushDelay) // Start the endless flush loop var rows pgx.Rows var err error - for true { + for { // Retrieve the list of all public databases dbQuery := ` SELECT users.user_name, db.db_name @@ -1524,7 +1525,7 @@ func FlushViewCount() { rows, err = pdb.Query(context.Background(), dbQuery) if err != nil { log.Printf("Database query failed: %v", err) - return + continue } var dbList []dbEntry for rows.Next() { @@ -1533,7 +1534,7 @@ func FlushViewCount() { if err != nil { log.Printf("Error retrieving database list for view count flush thread: %v", err) rows.Close() - return + continue } dbList = append(dbList, oneRow) } @@ -1580,7 +1581,9 @@ func FlushViewCount() { // Wait before running the loop again time.Sleep(Conf.Memcache.ViewCountFlushDelay * time.Second) } - return + + // If somehow the endless loop finishes, then record that in the server logs + log.Printf("%s: WARN: periodic view count flushing loop stopped.", Conf.Live.Nodename) } // ForkDatabase forks the PostgreSQL entry for a SQLite database from one user to another @@ -2856,7 +2859,7 @@ func LiveUserDBs(dbOwner string, public AccessType) (list []DBInfo, err error) { return nil, err } - // Ask the AMQP backend for the database file size + // Ask the job queue backend for the database file size oneRow.Size, err = LiveSize(liveNode, dbOwner, dbOwner, oneRow.Database) if err != nil { log.Printf("Error when retrieving size of live databases for user '%s': %v", dbOwner, err) @@ -3480,11 +3483,11 @@ func StatusUpdates(loggedInUser string) (statusUpdates map[string][]StatusUpdate func StatusUpdatesLoop() { // Ensure a warning message is displayed on the console if the status update loop exits defer func() { - log.Printf("WARN: Status update loop exited") + log.Printf("%s: WARN: Status update loop exited", Conf.Live.Nodename) }() // Log the start of the loop - log.Printf("Status update processing loop started. %d second refresh.", Conf.Event.Delay) + log.Printf("%s: status update processing loop started. %d second refresh.", Conf.Live.Nodename, Conf.Event.Delay) // Start the endless status update processing loop var err error @@ -5387,8 +5390,8 @@ func RecordWebLogin(userName string) (err error) { return } if numRows := commandTag.RowsAffected(); numRows != 1 { - err = errors.New(fmt.Sprintf("Wrong number of rows (%d) affected while adding a webUI login record for '%s' to the database", - numRows, SanitiseLogString(userName))) + err = fmt.Errorf("Wrong number of rows (%d) affected while adding a webUI login record for '%s' to the database", + numRows, SanitiseLogString(userName)) } return } diff --git a/common/postgresql_live.go b/common/postgresql_live.go new file mode 100644 index 000000000..aadf85833 --- /dev/null +++ b/common/postgresql_live.go @@ -0,0 +1,637 @@ +package common + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +var ( + // CheckJobQueue is used by the live daemons for triggering a check of the job queue + CheckJobQueue chan struct{} + + // CheckResponsesQueue is used by the non-live daemons for triggering a check of the job responses queue + CheckResponsesQueue chan struct{} + + // ResponseWaiters is used to direct job queue responses back to the appropriate callers + ResponseWaiters *ResponseReceivers + + // SubmitterInstance is a random string generated at server start for identification purposes + SubmitterInstance string +) + +// JobQueueCheck checks if newly submitted work is available for processing +func JobQueueCheck() { + + if JobQueueDebug > 0 { + log.Printf("%s: starting JobQueueCheck()...", Conf.Live.Nodename) + } + + // Loop around checking for newly submitted jobs + for _ = range CheckJobQueue { + if JobQueueDebug > 1 { // Only show when we have job queue debug verbosity turned up high + log.Printf("%s: JobQueueCheck() received event", Conf.Live.Nodename) + } + + // Retrieve job details from the database + ctx := context.Background() + tx, err := JobQueueConn.Begin(ctx) + if err != nil { + log.Printf("%s: error in JobQueueCheck(): %s", Conf.Live.Nodename, err) + continue + } + + // TODO: a) we're in a loop so can't use defer, and + // b) we'll likely need to update the job state to 'error' on failure instead of rolling back + + dbQuery := ` + SELECT job_id, operation, submitter_node, details + FROM job_submissions + WHERE state = 'new' + AND (target_node = 'any' OR target_node = $1) + AND completed_date IS NULL + ORDER BY submission_date ASC + FOR UPDATE SKIP LOCKED + LIMIT 1` + var jobID int + var details, subNode, op string + err = tx.QueryRow(ctx, dbQuery, Conf.Live.Nodename).Scan(&jobID, &op, &subNode, &details) + if err != nil { + // Ignore any "no rows in result set" error + if !errors.Is(err, pgx.ErrNoRows) { + log.Printf("%v: retrieve job details error: %v", Conf.Live.Nodename, err) + } else if JobQueueDebug > 1 { // Only show when we have job queue debug verbosity turned up high + log.Printf("%s: --- No jobs waiting for processing ---", Conf.Live.Nodename) + } + tx.Rollback(ctx) + continue + } + + if JobQueueDebug > 0 { + log.Printf("%s: picked up event for jobID = %d", Conf.Live.Nodename, jobID) + } + + // Change the "state" field for the job entry to something other than 'new' so it's not unintentionally + // picked up by future checks if something goes wrong before the job completes + dbQuery = ` + UPDATE job_submissions + SET state = 'in progress' + WHERE job_id = $1` + var t pgconn.CommandTag + var responsePayload []byte + t, err = tx.Exec(ctx, dbQuery, jobID) + if err != nil { + log.Printf("%s: error when updating job completion status to complete in backend database: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + // Safety check + numRows := t.RowsAffected() + if numRows != 1 { + msg := fmt.Sprintf("something went wrong when updating jobID '%d' to 'in progress', number of rows updated = %d", jobID, numRows) + log.Printf("%s: %s", Conf.Live.Nodename, msg) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, msg)) + } + + // Commit the transaction + err = tx.Commit(ctx) + if err != nil { + log.Println(err) + } + + // Unmarshal the job details + var req JobRequest + err = json.Unmarshal([]byte(details), &req) + if err != nil { + msg := fmt.Sprintf("error when unmarshalling job details: %v", err) + log.Printf("%s: %s", Conf.Live.Nodename, msg) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, msg)) + } + + // Perform the desired operation + switch op { + case "backup": + if JobQueueDebug > 0 { + log.Printf("%s: running [BACKUP] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return status of backup operation + err = SQLiteBackupLive(Conf.Live.StorageDir, req.DBOwner, req.DBName) + var response JobResponseDBError + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) // Use an empty error message to indicate success + if err != nil { + log.Printf("%s: error when serialising backup response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "columns": + if JobQueueDebug > 0 { + log.Printf("%s: running [COLUMNS] on '%s/%s': '%s'", Conf.Live.Nodename, req.DBOwner, req.DBName, req.Data) + } + + // Return the column list to the caller + columns, pk, err, errCode := SQLiteGetColumnsLive(Conf.Live.StorageDir, req.DBOwner, req.DBName, fmt.Sprintf("%s", req.Data)) + response := JobResponseDBColumns{Columns: columns, PkColumns: pk, ErrCode: errCode} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising the column list response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "createdb": + if JobQueueDebug > 0 { + log.Printf("%s: running [CREATE DATABASE] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return status of database creation + err = JobQueueCreateDatabase(req) + response := JobResponseDBCreate{NodeName: Conf.Live.Nodename} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) // Use an empty error message to indicate success + if err != nil { + log.Printf("%s: error when serialising create database response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "delete": + if JobQueueDebug > 0 { + log.Printf("%s: running [DELETE] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Delete the database file from the node + err = RemoveLiveDB(req.DBOwner, req.DBName) + var response JobResponseDBError + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising delete database response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "execute": + if JobQueueDebug > 0 { + log.Printf("%s: running [EXECUTE] on '%s/%s': '%s'", Conf.Live.Nodename, req.DBOwner, req.DBName, req.Data) + } + + // Execute a SQL statement on the database + rowsChanged, err := SQLiteExecuteQueryLive(Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) + response := JobResponseDBExecute{RowsChanged: rowsChanged} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising execute request response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "indexes": + if JobQueueDebug > 0 { + log.Printf("%s: running [INDEXES] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return the list of indexes + indexes, err := SQLiteGetIndexesLive(Conf.Live.StorageDir, req.DBOwner, req.DBName) + response := JobResponseDBIndexes{Indexes: indexes} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising index list response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "ping": + // TODO: Write a ping responder so we can internally check if live nodes are responding + + case "query": + if JobQueueDebug > 0 { + log.Printf("%s: running [QUERY] on '%s/%s': '%s'", Conf.Live.Nodename, req.DBOwner, req.DBName, req.Data) + } + + // Return the query result + rows, err := SQLiteRunQueryLive(Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) + response := JobResponseDBQuery{Results: rows} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising query response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "rowdata": + if JobQueueDebug > 0 { + log.Printf("%s: running [ROWDATA] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Decode the base64 request data back to JSON + b64, err := base64.StdEncoding.DecodeString(req.Data.(string)) + if err != nil { + msg := fmt.Sprintf("error when base64 decoding rowdata job details: %v", err) + log.Printf("%s: %s", Conf.Live.Nodename, msg) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, msg)) + break + } + + // Extract the request information + var reqData JobRequestRows + err = json.Unmarshal(b64, &reqData) + if err != nil { + msg := fmt.Sprintf("error when unmarshalling rowdata job details: %v", err) + log.Printf("%s: %s", Conf.Live.Nodename, msg) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, msg)) + break + } + dbTable := reqData.DbTable + sortCol := reqData.SortCol + sortDir := reqData.SortDir + commitID := reqData.CommitID + maxRows := reqData.MaxRows + rowOffset := reqData.RowOffset + + // Read the desired row data and return it to the caller + var tmpErr error + resp := JobResponseDBRows{RowData: SQLiteRecordSet{}} + resp.Tables, resp.DefaultTable, resp.RowData, resp.DatabaseSize, tmpErr = + SQLiteReadDatabasePage("", "", req.RequestingUser, req.DBOwner, req.DBName, dbTable, sortCol, sortDir, commitID, rowOffset, maxRows, true) + if tmpErr != nil { + resp.Err = tmpErr.Error() + } + responsePayload, err = json.Marshal(resp) + if err != nil { + log.Printf("%s: error when serialising row data response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "size": + if JobQueueDebug > 0 { + log.Printf("%s: running [SIZE] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return the on disk size of the database + size, err := JobQueueGetSize(req.DBOwner, req.DBName) + response := JobResponseDBSize{Size: size} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising size check response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "tables": + if JobQueueDebug > 0 { + log.Printf("%s: running [TABLES] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return the list of tables + tables, err := SQLiteGetTablesLive(Conf.Live.StorageDir, req.DBOwner, req.DBName) + response := JobResponseDBTables{Tables: tables} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising table list response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + case "views": + if JobQueueDebug > 0 { + log.Printf("%s: running [VIEWS] on '%s/%s'", Conf.Live.Nodename, req.DBOwner, req.DBName) + } + + // Return the list of views + views, err := SQLiteGetViewsLive(Conf.Live.StorageDir, req.DBOwner, req.DBName) + response := JobResponseDBViews{Views: views} + if err != nil { + response.Err = err.Error() + } + responsePayload, err = json.Marshal(response) + if err != nil { + log.Printf("%s: error when serialising view list response json: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + default: + log.Printf("%v: notification received for unhandled operation '%s'\n", Conf.Live.Nodename, op) + } + + // Update the job completion status in the backend database + dbQuery = ` + UPDATE job_submissions + SET state = 'complete', completed_date = now() + WHERE job_id = $1` + t, err = JobQueueConn.Exec(ctx, dbQuery, jobID) + if err != nil { + log.Printf("%s: error when updating job completion status to complete in backend database: %s", Conf.Live.Nodename, err) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, err)) + } + + // Safety check + numRows = t.RowsAffected() + if numRows != 1 { + msg := fmt.Sprintf("something went wrong when updating jobID '%d' to 'complete', number of rows updated = %d", jobID, numRows) + log.Printf("%s: %s", Conf.Live.Nodename, msg) + responsePayload = []byte(fmt.Sprintf(`{"error": "%s"}`, msg)) + } + + // Add the response to the backend job queue database + err = ResponseSubmit(jobID, subNode, responsePayload) + if err != nil { + log.Println(err) + } + } +} + +// JobQueueCreateDatabase creates a database on a live node +func JobQueueCreateDatabase(req JobRequest) (err error) { + // Set up the live database locally + _, err = LiveRetrieveDatabaseMinio(Conf.Live.StorageDir, req.DBOwner, req.DBName, req.Data.(string)) + if err != nil { + log.Println(err) + // TODO: Update the job status to failed and notify the caller + return + } + return +} + +// JobQueueGetSize returns the on disk size of a database on a live node +func JobQueueGetSize(DBOwner, DBName string) (size int64, err error) { + dbPath := filepath.Join(Conf.Live.StorageDir, DBOwner, DBName, "live.sqlite") + var db os.FileInfo + db, err = os.Stat(dbPath) + if err != nil { + return + } + + // Return the database size to the caller + size = db.Size() + return +} + +// JobQueueListen listens for database notify events indicating newly submitted jobs +func JobQueueListen() { + if JobQueueDebug > 0 { + // Log the start of the loop + log.Printf("%v: started JobQueueListen()", Conf.Live.Nodename) + } + + // Listen for notify events + _, err := JobListenConn.Exec(context.Background(), "LISTEN job_submissions_queue") + if err != nil { + log.Fatal(err) + } + + // Start the endless loop handling database notifications + for { + _, err := JobListenConn.WaitForNotification(context.Background()) + if err != nil { + log.Printf("%s: error in JobQueueListen(): %s", Conf.Live.Nodename, err) + } + + // Send an event to the goroutine that checks for submitted jobs + CheckJobQueue <- struct{}{} + } + return +} + +// JobSubmit submits job details to our PostgreSQL based job queue +func JobSubmit[T any](response *T, targetNode, operation, requestingUser, dbOwner, dbName string, data interface{}) (err error) { + // Format the request details into a JSON structure + req := JobRequest{ + Operation: operation, + DBOwner: dbOwner, + DBName: dbName, + Data: data, + RequestingUser: requestingUser, + } + var details []byte + details, err = json.Marshal(req) + if err != nil { + log.Println(err) + return + } + + // Start a new transaction + ctx := context.Background() + tx, err := JobQueueConn.Begin(ctx) + if err != nil { + log.Println(err) + return + } + defer tx.Rollback(ctx) + + // Safety check + if SubmitterInstance == "" { + err = fmt.Errorf("%s: ERROR - JobSubmit() called before SubmitterInstance was set", Conf.Live.Nodename) + return + } + + // Insert the job details + dbQuery := ` + INSERT INTO job_submissions (target_node, operation, submitter_node, details) + VALUES ($1, $2, $3, $4) + RETURNING job_id` + var jobID int + err = tx.QueryRow(ctx, dbQuery, targetNode, operation, SubmitterInstance, details).Scan(&jobID) + if err != nil { + log.Printf("%s: error when adding a job to the backend job submission table: %v", Conf.Live.Nodename, err) + return + } + + // Double check the job was submitted ok + if jobID == 0 { + // Something went wrong when adding the new job + err = fmt.Errorf("%s: something went wrong when adding the new job to the queue. Returned job_id was 0", Conf.Live.Nodename) + return + } + + // Commit the transaction + tx.Commit(ctx) + + if JobQueueDebug > 0 { + log.Printf("%s: job '%d' added to queue", Conf.Live.Nodename, jobID) + } + + // Wait for response + err = WaitForResponse(jobID, &response) + if err != nil { + return + } + return +} + +// ResponseQueueCheck checks if a newly submitted response is available for processing +func ResponseQueueCheck() { + + if JobQueueDebug > 0 { + log.Printf("%s: starting ResponseQueueCheck()...", Conf.Live.Nodename) + } + + // Loop around checking for newly submitted responses + for range CheckResponsesQueue { + + if JobQueueDebug > 0 { + log.Printf("%s: responseQueueCheck() received event", Conf.Live.Nodename) + } + + // Check for new responses here + dbQuery := ` + SELECT response_id, job_id, details + FROM job_responses + WHERE processed_date IS NULL + AND submitter_node = $1 + ORDER BY response_date ASC` + var jobID, responseID int + var details string + ctx := context.Background() + rows, err := JobQueueConn.Query(ctx, dbQuery, SubmitterInstance) + if err != nil { + // Ignore any "no rows in result set" error + if !errors.Is(err, pgx.ErrNoRows) { + log.Printf("%v: retrieve response details error: %v", Conf.Live.Nodename, err) + } + continue + } + + // For each new response, send its details to any matching waiting caller + _, err = pgx.ForEachRow(rows, []any{&responseID, &jobID, &details}, func() error { + if JobQueueDebug > 0 { + log.Printf("%s: picked up response %d for jobID %d", Conf.Live.Nodename, responseID, jobID) + } + + ResponseWaiters.Lock() + receiverChan, ok := ResponseWaiters.receivers[jobID] + if ok { + *receiverChan <- ResponseInfo{jobID: jobID, responseID: responseID, payload: details} + } + ResponseWaiters.Unlock() + return nil + }) + if err != nil { + log.Printf("%s: error in ResponseQueueCheck when running pgx.ForEachRow(): '%v' ", Conf.Live.Nodename, err) + continue + } + } +} + +// ResponseQueueListen listens for database notify events with responses from the other DBHub.io daemons +func ResponseQueueListen() { + if JobQueueDebug > 0 { + // Log the start of the loop + log.Printf("%v: started ResponseQueueListen()", Conf.Live.Nodename) + } + + // Listen for notify events + _, err := JobListenConn.Exec(context.Background(), "LISTEN job_responses_queue") + if err != nil { + log.Fatal(err) + } + + // Start the endless loop handling database notifications + for { + n, err := JobListenConn.WaitForNotification(context.Background()) + if err != nil { + log.Printf("%s: error in ResponseQueueListen(): %s", Conf.Live.Nodename, err) + } + + if JobQueueDebug > 0 && n.Payload == SubmitterInstance { + log.Printf("%s: picked up response notification for submitter '%s'", Conf.Live.Nodename, n.Payload) + } + + // Send an event to the response checking goroutine, letting it know there's a new response available + if n.Payload == SubmitterInstance { + CheckResponsesQueue <- struct{}{} + } + } +} + +// ResponseComplete marks a response as processed +func ResponseComplete(responseID int) (err error) { + // Start a new transaction + ctx := context.Background() + tx, err := JobQueueConn.Begin(ctx) + if err != nil { + log.Println(err) + return + } + defer tx.Rollback(ctx) + + // Insert the response + dbQuery := ` + UPDATE job_responses + SET processed_date = now() + WHERE response_id = $1` + tag, err := tx.Exec(ctx, dbQuery, responseID) + if err != nil { + log.Printf("%s: error when updating a response in the backend job responses table: %v", Conf.Live.Nodename, err) + return + } + + // Double check the response was updated ok + numRows := tag.RowsAffected() + if numRows != 1 { + err = fmt.Errorf("%s: something went wrong when updating a response in the job responses table. Number of rows affected (%d) wasn't 1'", Conf.Live.Nodename, numRows) + return + } + + // Commit the transaction + tx.Commit(ctx) + return +} + +// ResponseSubmit adds a response to the job_responses table +func ResponseSubmit(jobID int, submitterNode string, payload []byte) (err error) { + // Start a new transaction + ctx := context.Background() + tx, err := JobQueueConn.Begin(ctx) + if err != nil { + log.Println(err) + return + } + defer tx.Rollback(ctx) + + // Insert the response details + dbQuery := ` + INSERT INTO job_responses (job_id, submitter_node, details) + VALUES ($1, $2, $3)` + tag, err := tx.Exec(ctx, dbQuery, jobID, submitterNode, payload) + if err != nil { + log.Printf("%s: error when adding a response to the backend job responses table: %v", Conf.Live.Nodename, err) + return + } + + // Double check the response was added ok + numRows := tag.RowsAffected() + if numRows != 1 { + err = fmt.Errorf("%s: something went wrong when adding the new response to the job responses table. Number of rows (%d) wasn't 1", Conf.Live.Nodename, numRows) + return + } + + // Commit the transaction + tx.Commit(ctx) + return +} diff --git a/common/responses.go b/common/responses.go index c8e510cc3..6319de4a1 100644 --- a/common/responses.go +++ b/common/responses.go @@ -36,7 +36,7 @@ func BranchListResponse(dbOwner, dbName string) (list BranchListResponseContaine return } -// ExecuteResponseContainer is used by our AMQP backend, to return information in response to an +// ExecuteResponseContainer is used by our job queue backend, to return information in response to an // Execute() call on a live database. It holds the success/failure status of the remote call, // and also the number of rows changed by the Execute() call (if it succeeded) type ExecuteResponseContainer struct { diff --git a/common/sqlite.go b/common/sqlite.go index 53a11c6b0..56b40dd29 100644 --- a/common/sqlite.go +++ b/common/sqlite.go @@ -1022,7 +1022,7 @@ func ReadSQLiteDBCSV(sdb *sqlite.Conn, dbTable string) ([][]string, error) { return resultSet, nil } -// SQLiteBackupLive is used by our AMQP backend nodes to refresh a live SQLite database back into Minio +// SQLiteBackupLive is used by our job queue backend nodes to refresh a live SQLite database back into Minio func SQLiteBackupLive(baseDir, dbOwner, dbName string) (err error) { dbPath := filepath.Join(baseDir, dbOwner, dbName, "live.sqlite") if _, err = os.Stat(dbPath); err != nil { @@ -1109,7 +1109,7 @@ func SQLiteBackupLive(baseDir, dbOwner, dbName string) (err error) { return } -// SQLiteExecuteQueryLive is used by our AMQP backend infrastructure to execute a user provided SQLite statement +// SQLiteExecuteQueryLive is used by our job queue backend infrastructure to execute a user provided SQLite statement func SQLiteExecuteQueryLive(baseDir, dbOwner, dbName, loggedInUser, query string) (rowsChanged int, err error) { // Open the Live database on the local node var sdb *sqlite.Conn @@ -1125,16 +1125,18 @@ func SQLiteExecuteQueryLive(baseDir, dbOwner, dbName, loggedInUser, query string // Execute the statement rowsChanged, err = sdb.ExecDml(query) if err != nil { - log.Printf("Error when executing query by '%s' for LIVE database (%s/%s): '%s'", - SanitiseLogString(loggedInUser), SanitiseLogString(dbOwner), SanitiseLogString(dbName), - SanitiseLogString(err.Error())) + if !strings.HasPrefix(err.Error(), "don't use exec with") { + log.Printf("Error when executing query by '%s' for LIVE database (%s/%s): '%s'", + SanitiseLogString(loggedInUser), SanitiseLogString(dbOwner), SanitiseLogString(dbName), + SanitiseLogString(err.Error())) + } return } return } -// SQLiteGetColumnsLive is used by our AMQP backend nodes to retrieve the list of columns from a SQLite database -func SQLiteGetColumnsLive(baseDir, dbOwner, dbName, table string) (columns []sqlite.Column, pk []string, err error, errCode AMQPErrorCode) { +// SQLiteGetColumnsLive is used by our job queue backend nodes to retrieve the list of columns from a SQLite database +func SQLiteGetColumnsLive(baseDir, dbOwner, dbName, table string) (columns []sqlite.Column, pk []string, err error, errCode JobQueueErrorCode) { // Open the database on the local node var sdb *sqlite.Conn sdb, err = OpenSQLiteDatabaseLive(baseDir, dbOwner, dbName) @@ -1157,7 +1159,7 @@ func SQLiteGetColumnsLive(baseDir, dbOwner, dbName, table string) (columns []sql } if !tableOrViewFound { err = errors.New("Provided table or view name doesn't exist in this database") - errCode = AMQPRequestedTableNotPresent + errCode = JobQueueRequestedTableNotPresent return } @@ -1173,7 +1175,7 @@ func SQLiteGetColumnsLive(baseDir, dbOwner, dbName, table string) (columns []sql return } -// SQLiteGetIndexesLive is used by our AMQP backend nodes to retrieve the list of indexes from a SQLite database +// SQLiteGetIndexesLive is used by our job queue backend nodes to retrieve the list of indexes from a SQLite database func SQLiteGetIndexesLive(baseDir, dbOwner, dbName string) (indexes []APIJSONIndex, err error) { // Open the database on the local node var sdb *sqlite.Conn @@ -1213,7 +1215,7 @@ func SQLiteGetIndexesLive(baseDir, dbOwner, dbName string) (indexes []APIJSONInd return } -// SQLiteGetTablesLive is used by our AMQP backend nodes to retrieve the list of tables in a SQLite database +// SQLiteGetTablesLive is used by our job queue backend nodes to retrieve the list of tables in a SQLite database func SQLiteGetTablesLive(baseDir, dbOwner, dbName string) (tables []string, err error) { // Open the database on the local node var sdb *sqlite.Conn @@ -1231,7 +1233,7 @@ func SQLiteGetTablesLive(baseDir, dbOwner, dbName string) (tables []string, err return } -// SQLiteGetViewsLive is used by our AMQP backend nodes to retrieve the list of views in a SQLite database +// SQLiteGetViewsLive is used by our job queue backend nodes to retrieve the list of views in a SQLite database func SQLiteGetViewsLive(baseDir, dbOwner, dbName string) (views []string, err error) { // Open the database on the local node var sdb *sqlite.Conn @@ -1625,7 +1627,7 @@ func SQLiteRunQueryDefensive(w http.ResponseWriter, r *http.Request, querySource return dataRows, err } -// SQLiteRunQueryLive is used by our AMQP backend infrastructure to run a user provided SQLite query +// SQLiteRunQueryLive is used by our job queue backend infrastructure to run a user provided SQLite query func SQLiteRunQueryLive(baseDir, dbOwner, dbName, loggedInUser, query string) (records SQLiteRecordSet, err error) { // Open the database on the local node var sdb *sqlite.Conn diff --git a/common/util.go b/common/util.go index 57bca329c..3b781cfbc 100644 --- a/common/util.go +++ b/common/util.go @@ -903,7 +903,7 @@ func DownloadDatabase(w http.ResponseWriter, r *http.Request, dbOwner, dbName, c var userDB *minio.Object var logStr string if isLive { - // It's a live database, so we tell the AMQP backend to back it up into Minio, which we then provide to the user + // It's a live database, so we tell the job queue backend to back it up into Minio, which we then provide to the user err = LiveBackup(liveNode, loggedInUser, dbOwner, dbName) if err != nil { return diff --git a/database/migrations/000005_job_submission_tables.down.sql b/database/migrations/000005_job_submission_tables.down.sql new file mode 100644 index 000000000..aa959f0ef --- /dev/null +++ b/database/migrations/000005_job_submission_tables.down.sql @@ -0,0 +1,8 @@ +BEGIN; +DROP TABLE IF EXISTS job_submissions; +DROP TABLE IF EXISTS job_responses; +DROP TRIGGER IF EXISTS job_submissions_trigger ON job_submissions; +DROP TRIGGER IF EXISTS job_responses_trigger ON job_responses; +DROP FUNCTION IF EXISTS job_submissions_notify(); +DROP FUNCTION IF EXISTS job_responses_notify(); +COMMIT; \ No newline at end of file diff --git a/database/migrations/000005_job_submission_tables.up.sql b/database/migrations/000005_job_submission_tables.up.sql new file mode 100644 index 000000000..af3b9ff91 --- /dev/null +++ b/database/migrations/000005_job_submission_tables.up.sql @@ -0,0 +1,58 @@ +BEGIN; + +-- job_submissions table +CREATE TABLE IF NOT EXISTS job_submissions ( + job_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + submission_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + target_node TEXT NOT NULL, + submitter_node TEXT NOT NULL, + operation TEXT NOT NULL, + details JSONB, + state TEXT NOT NULL DEFAULT 'new'::TEXT, + completed_date TIMESTAMP WITH TIME ZONE +); + +-- job_responses table +CREATE TABLE IF NOT EXISTS job_responses +( + response_id BIGINT GENERATED BY DEFAULT AS IDENTITY + CONSTRAINT job_responses_pk + PRIMARY KEY, + job_id BIGINT NOT NULL + CONSTRAINT job_responses_job_submissions_job_id_fk + REFERENCES job_submissions, + response_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + submitter_node TEXT NOT NULL, + details JSONB NOT NULL, + processed_date TIMESTAMP WITH TIME ZONE +); + +-- notify function for the job_submissions table +CREATE OR REPLACE FUNCTION job_submissions_notify() + RETURNS trigger AS $$ +BEGIN + PERFORM pg_notify('job_submissions_queue', ''); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- notify function for the job_responses table +CREATE OR REPLACE FUNCTION job_responses_notify() + RETURNS trigger AS $$ +BEGIN + PERFORM pg_notify('job_responses_queue', NEW.submitter_node); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- trigger function for the job_submissions table +CREATE OR REPLACE TRIGGER job_submissions_trigger + AFTER INSERT ON job_submissions +EXECUTE FUNCTION job_submissions_notify(); + +-- trigger function for the job_responses table +CREATE OR REPLACE TRIGGER job_responses_trigger + AFTER INSERT ON job_responses + FOR EACH ROW EXECUTE FUNCTION job_responses_notify(); + +COMMIT; \ No newline at end of file diff --git a/db4s/main.go b/db4s/main.go index d759fd1df..d8269c3cc 100644 --- a/db4s/main.go +++ b/db4s/main.go @@ -38,6 +38,9 @@ func main() { log.Fatalf("Configuration file problem: '%s'", err) } + // Set the node name used in various logging strings + com.Conf.Live.Nodename = "DB4S end point server" + // Set the temp dir environment variable err = os.Setenv("TMPDIR", com.Conf.DiskCache.Directory) if err != nil { @@ -119,7 +122,7 @@ func main() { } // Start server - log.Printf("Starting DB4S end point on %s", server) + log.Printf("%s: listening for requests on %s", com.Conf.Live.Nodename, server) log.Fatal(newServer.ListenAndServeTLS(com.Conf.DB4S.Certificate, com.Conf.DB4S.CertificateKey)) } diff --git a/docker/Dockerfile b/docker/Dockerfile index 1310cf5a0..9cc79ed6b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,26 +1,10 @@ # vim:set ft=dockerfile: -# We grab RabbitMQ and Erlang from the official RabbitMQ image -FROM rabbitmq:3-alpine AS rabbit - # Build our own image FROM alpine:3.19 LABEL maintainer="Justin Clift " -# Copy the RabbitMQ files we need -COPY --from=rabbit /etc/rabbitmq /etc/rabbitmq -COPY --from=rabbit /opt/erlang /opt/erlang -COPY --from=rabbit /opt/openssl /opt/openssl -COPY --from=rabbit /opt/rabbitmq /opt/rabbitmq -COPY --from=rabbit /var/lib/rabbitmq /var/lib/rabbitmq -COPY --from=rabbit /var/log/rabbitmq /var/log/rabbitmq - -# Create the rabbitmq user and group -RUN addgroup -S rabbitmq && \ - adduser -S -D -h /var/lib/rabbitmq -s /sbin/nologin -g "Linux User,,," -G rabbitmq rabbitmq && \ - chown -Rh rabbitmq: /opt/rabbitmq /var/log/rabbitmq - # Use a fast Australian mirror for the Alpine package repositories # Without doing this, building the image can take 2+ hours. :( RUN echo "https://mirror.aarnet.edu.au/pub/alpine/v3.19/main" > /etc/apk/repositories && \ @@ -50,7 +34,7 @@ ENV DBHUB_SOURCE /dbhub.io ENV MINIO_ROOT_USER minio ENV MINIO_ROOT_PASSWORD minio123 -# Run each of our (non RabbitMQ) daemon dependencies at least once to ensure they initialise ok, and populate the DBHub.io database +# Run each of our daemon dependencies at least once to ensure they initialise ok, and populate the DBHub.io database RUN echo "echo export PGDATA=/var/lib/postgresql/data > ~postgres/.profile" >> /usr/local/bin/init.sh && \ echo "echo export MINIO_ROOT_USER=${MINIO_ROOT_USER} > ~minio/.profile" >> /usr/local/bin/init.sh && \ echo "echo export MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} >> ~minio/.profile" >> /usr/local/bin/init.sh && \ @@ -91,13 +75,8 @@ RUN echo "echo 127.0.0.1 docker-dev.dbhub.io docker-dev >> /etc/hosts" >> /usr/l echo "su - minio -c '/usr/bin/minio server --quiet --anonymous /var/lib/minio/data 2>&1 &'" >> /usr/local/bin/start.sh && \ echo "su - postgres -c '/usr/libexec/postgresql/pg_ctl start'" >> /usr/local/bin/start.sh && \ echo "" >> /usr/local/bin/start.sh && \ - echo "unset CONFIG_FILE" >> /usr/local/bin/start.sh && \ - echo "export RABBITMQ_CONFIG_FILES=/etc/rabbitmq/conf.d" >> /usr/local/bin/start.sh && \ - echo "export PATH=/opt/rabbitmq/sbin:/opt/erlang/bin:/opt/openssl/bin:$PATH" >> /usr/local/bin/start.sh && \ - echo "/opt/rabbitmq/sbin/rabbitmq-server &" >> /usr/local/bin/start.sh && \ - echo "" >> /usr/local/bin/start.sh && \ - echo "# Wait for RabbitMQ to start before launching the DBHub.io daemons" >> /usr/local/bin/start.sh && \ - echo "sleep 15" >> /usr/local/bin/start.sh && \ + echo "# Delay long enough for the DBHub.io daemons to start" >> /usr/local/bin/start.sh && \ + echo "sleep 1" >> /usr/local/bin/start.sh && \ echo "" >> /usr/local/bin/start.sh && \ echo "su - dbhub -c 'if [ -f "${DBHUB_SOURCE}/.env" ]; then source ${DBHUB_SOURCE}/.env; fi; CONFIG_FILE=${CONFIG_FILE} /usr/local/bin/dbhub-webui >>/home/dbhub/output.log 2>&1 &'" >> /usr/local/bin/start.sh && \ echo "su - dbhub -c 'if [ -f "${DBHUB_SOURCE}/.env" ]; then source ${DBHUB_SOURCE}/.env; fi; CONFIG_FILE=${CONFIG_FILE} /usr/local/bin/dbhub-api >>/home/dbhub/output.log 2>&1 &'" >> /usr/local/bin/start.sh && \ diff --git a/docker/README.md b/docker/README.md index ab7046c35..b9fccfa85 100644 --- a/docker/README.md +++ b/docker/README.md @@ -9,17 +9,16 @@ It includes the four DBHub.io daemons: * The webUI, listening on port 9443 * The REST API end point, listening on port 9444 * The DB4S end point (the daemon DB Browser for SQLite talks to) on port 5550 -* The internal-use-only "live" database daemon, +* The internal-use-only "live" database daemon (running two instances), ...and the dependencies for the daemons: * PostgreSQL * Memcached * Minio -* RabbitMQ This is done as an all-in-one image for now. It _might_ be better separated -into separate services per damon (eg for docker-compose), but that'll be a +into separate services per daemon (eg for docker-compose), but that'll be a later thing (if needed). diff --git a/docker/config.toml b/docker/config.toml index 9640ddca8..420ca9c2a 100644 --- a/docker/config.toml +++ b/docker/config.toml @@ -45,14 +45,6 @@ access_key = "minio" secret = "minio123" https = false -[mq] -cert_file = "" -key_file = "" -password = "guest" -port = 5672 -server = "localhost" -username = "guest" - [pg] database = "dbhub" num_connections = 5 diff --git a/go.mod b/go.mod index ddcf97f62..8217476f9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sqlitebrowser/dbhub.io -go 1.17 +go 1.18 replace ( github.com/Sirupsen/logrus v1.0.5 => github.com/sirupsen/logrus v1.0.5 @@ -24,7 +24,6 @@ require ( github.com/minio/minio-go v6.0.14+incompatible github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 - github.com/rabbitmq/amqp091-go v1.7.0 github.com/segmentio/ksuid v1.0.3 github.com/smtp2go-oss/smtp2go-go v1.0.2 github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 diff --git a/go.sum b/go.sum index c52fab2aa..ffbd003f6 100644 --- a/go.sum +++ b/go.sum @@ -1,557 +1,30 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.0/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.1/go.mod h1:+nVKciyKD2J9TyVcEQ82Bo9b+3F92PiQfHrIE/zqLqM= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= -github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= github.com/aquilax/truncate v1.0.0 h1:UgIGS8U/aZ4JyOJ2h3xcF5cSQ06+gGBnjxH2RUHJe0U= github.com/aquilax/truncate v1.0.0/go.mod h1:BeMESIDMlvlS3bmg4BVvBbbZUNwWtS8uzYPAKXwwhLw= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= -github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= -github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= -github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.4.0/go.mod h1:eHwXu2+uE/T6gpnYWwBwqoeqRf9IXyCcolyOWDRAErQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4/go.mod h1:Ex7XQmbFmgFHrjUX6TN3mApKW5Hglyga+F7wZHTtYhA= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= -github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.2/go.mod h1:EASdTcM1lGhUe1/p4gkojHwlGJkeoRjjr1sRCzup3Is= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.2/go.mod h1:QuL2Ym8BkrLmN4lUofXYq6000/i5jPjosCNK//t6gak= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.2/go.mod h1:np7TMuJNT83O0oDOSF8i4dF3dvGqA6hPYYo6YYkzgRA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.12.0/go.mod h1:6J++A5xpo7QDsIeSqPK4UHqMSyPOCopa+zKtqAMhqVQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.16.1/go.mod h1:CQe/KvWV1AqRc65KqeJjrLzr5X2ijnFTTVzJW0VBRCI= -github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= -github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= -github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1 h1:4QHxgr7hM4gVD8uOwrk8T1fjkKRLwaLjmTkU0ibhZKU= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dhui/dktest v0.3.15 h1:++YALfR3IipyvQU3YMrTQ3EKCEpaoE2XxL2dMmcmo1E= -github.com/dhui/dktest v0.3.15/go.mod h1:mcpXhM4AYf4JHrY4cMQBL1hdzGi7m4QHDteDn5XupSk= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= -github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.6.13/go.mod h1:qEySVqXrEugbHKvmhI8ZqtQi75/RHSSRNpffvB4I6Bw= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= -github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.56.0 h1:6HjxSjqdmgnujDPhlzR4a44lxK3w03WPN8te0SoUSeM= github.com/go-ini/ini v1.56.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= @@ -560,1237 +33,149 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-migrate/migrate/v4 v4.15.3-0.20230407054901-84009cf2ab46 h1:wF34g3t0fBF/yu/dpDHecqF/jZKXR2V9K+VoPDRvjFM= github.com/golang-migrate/migrate/v4 v4.15.3-0.20230407054901-84009cf2ab46/go.mod h1:ktBlRGa1T9EEQn+x55HoDgn5lCnDSK5w0ed1tUgDc8E= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= -github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/gwenn/gosqlite v0.0.0-20200521090053-24878be1a237 h1:RY/HYWiI6K/HL8ePZYpgvMGf4wX3/FgGxgE8kPh8vDM= github.com/gwenn/gosqlite v0.0.0-20200521090053-24878be1a237/go.mod h1:WBYs9HfQGOYDCz7rFwMk7aHkbTTB0cUkQe3pZQARvIg= github.com/gwenn/yacr v0.0.0-20200110180258-a66d8c42d0ff/go.mod h1:5SNcBGxZ5OaJAMJCSI/x3V7SGsvXqbwnwP/sHZLgYsw= github.com/gwenn/yacr v0.0.0-20200112083327-bbe82c1f4d60 h1:JX4Yy6S9U/f3Jix82M58NLIAFeW/UjBFVrnYn5GS4X8= github.com/gwenn/yacr v0.0.0-20200112083327-bbe82c1f4d60/go.mod h1:Ps/gikIXcn2rRmeP0HQ9EvUYJrfrjAi51Wg8acsrkP0= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= -github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= github.com/microcosm-cc/bluemonday v1.0.16 h1:kHmAq2t7WPWLjiGvzKa5o3HzSfahUKiOq7fAPUiMNIc= github.com/microcosm-cc/bluemonday v1.0.16/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= -github.com/microsoft/go-mssqldb v0.15.0/go.mod h1:Wr+jfynAR4lYmHA093AL8njUw2T6ovxe2jjBQKxBIco= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= -github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= -github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= -github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rabbitmq/amqp091-go v1.7.0 h1:V5CF5qPem5OGSnEo8BoSbsDGwejg6VUJsKEdneaoTUo= -github.com/rabbitmq/amqp091-go v1.7.0/go.mod h1:wfClAtY0C7bOHxd3GjmF26jEHn+rR/0B3+YV+Vn9/NI= -github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY= github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 h1:qb9IthCFBmROJ6YBS31BEMeSYjOscSiG+EO+JVNTz64= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 h1:SpaoQDTgpo2YZkvmr2mtgloFFfPTjtLMlZkQtNAPQik= github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ= -github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ= github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E= github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c h1:p3w+lTqXulfa3aDeycxmcLJDNxyUB89gf2/XqqK3eO0= github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU= -github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w= github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg= github.com/smtp2go-oss/smtp2go-go v1.0.2 h1:vXkqx9kyoQIuetyV3nm40b+OZevihhgb78X4vA/u2fs= github.com/smtp2go-oss/smtp2go-go v1.0.2/go.mod h1:lkv36awQXRBWAvnd517FFESKvne8465KCu90lPThcEY= -github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/sqlitebrowser/blackfriday v9.0.0+incompatible h1:ddH/UyzasooYgGIblVU4R8DdmBuJ7QXLvSqX/0chZv4= github.com/sqlitebrowser/blackfriday v9.0.0+incompatible/go.mod h1:/zga9sqpWzcewuI83AO5JZwe9+6F9GgPDdqqdNNEL/0= github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 h1:s0+Ea95n1LrsKh6rtclU/9Qb2/5ofvnfnR7gDDiFTw8= github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47/go.mod h1:8vPIKi5FslxCXEgfQxrFtWfdclGy6VWAc9NA1ZTYCJg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -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/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908150016-7ac13a9a928d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= -gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= -modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= -modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= -modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/sqlite v1.18.0/go.mod h1:B9fRWZacNxJBHoCJZQr1R54zhVn3fjfl0aszflrTSxY= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/live/main.go b/live/main.go index 1be252d8d..2bae81573 100644 --- a/live/main.go +++ b/live/main.go @@ -2,21 +2,11 @@ package main // Internal daemon for running SQLite queries sent by the other DBHub.io daemons -// FIXME: Note that all incoming AMQP requests _other_ than for database creation -// are handled by the same single goroutine. This should be changed to -// something smarter, such as using a pool of worker goroutines to handle -// the requests. - import ( - "encoding/json" "errors" - "fmt" - "io/fs" "log" "os" - "path/filepath" - sqlite "github.com/gwenn/gosqlite" com "github.com/sqlitebrowser/dbhub.io/common" ) @@ -58,418 +48,40 @@ func main() { log.Fatal(err) } - // Connect to PostgreSQL server + // Connect to the main PostgreSQL server err = com.ConnectPostgreSQL() if err != nil { log.Fatal(err) } - // Connect to MQ server - ch, err := com.ConnectMQ() - if err != nil { - log.Fatal(err) - } - - // Create queue for receiving new database creation requests - createQueue, err := com.MQCreateDBQueue(ch) - if err != nil { - log.Fatal(err) - } - - // Start consuming database creation requests - createDBMsgs, err := ch.Consume(createQueue.Name, "", false, false, false, false, nil) + // Connect to the job queue + com.CheckJobQueue = make(chan struct{}) + err = com.ConnectQueue() if err != nil { log.Fatal(err) } - go func() { - for d := range createDBMsgs { - // Decode JSON request - var req com.LiveDBRequest - err = json.Unmarshal(d.Body, &req) - if err != nil { - log.Println(err) - err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") - if err != nil { - log.Printf("Error: occurred on live node '%s' in the create db code, while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - // Verify that the object ID was passed through the interface correctly - objectID, ok := req.Data.(string) - if !ok { - err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") - if err != nil { - log.Printf("Error: occurred on live node '%s' in the create db code, while converting the Minio object ID to a string: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - // Set up the live database locally - _, err = com.LiveRetrieveDatabaseMinio(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, objectID) - if err != nil { - log.Println(err) - err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") - if err != nil { - log.Printf("Error: occurred on live node '%s' in the create db code, while constructing an AMQP error message response (location 2): '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - // Respond to the creation request with a success message - err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "success") - if err != nil { - continue - } - } - }() - - // Create the queue for receiving database queries - queryQueue, err := com.MQCreateQueryQueue(ch, com.Conf.Live.Nodename) - if err != nil { - log.Fatal(err) - } - - // Start consuming database query requests - requests, err := ch.Consume(queryQueue.Name, "", false, false, false, false, nil) - if err != nil { - log.Fatal(err) - } - go func() { - for msg := range requests { - if com.AmqpDebug > 1 { - log.Printf("'%s' received AMQP REQUEST (of not-yet-determined type)", com.Conf.Live.Nodename) - } - - // Decode JSON request - var req com.LiveDBRequest - err = json.Unmarshal(msg.Body, &req) - if err != nil { - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} - err = com.MQResponse("NOT-YET-DETERMINED", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' the main live node switch{} while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 1 { - log.Printf("Decoded request on '%s'. Correlation ID: '%s', request operation: '%s', request query: '%v'", com.Conf.Live.Nodename, msg.CorrelationId, req.Operation, req.Data) - } else if com.AmqpDebug == 1 { - log.Printf("Decoded request on '%s'. Correlation ID: '%s', request operation: '%s'", com.Conf.Live.Nodename, msg.CorrelationId, req.Operation) - } - - // Handle each operation - switch req.Operation { - case "backup": - err = com.SQLiteBackupLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) - if err != nil { - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} - err = com.MQResponse("BACKUP", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [BACKUP] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return a success message to the caller - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: ""} // Use an empty error message to indicate success - err = com.MQResponse("BACKUP", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP backup response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "columns": - columns, pk, err, errCode := com.SQLiteGetColumnsLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, fmt.Sprintf("%s", req.Data)) - if err != nil { - resp := com.LiveDBColumnsResponse{Node: com.Conf.Live.Nodename, Columns: []sqlite.Column{}, PkColumns: nil, Error: err.Error(), ErrCode: errCode} - err = com.MQResponse("COLUMNS", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [COLUMNS] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) - } - - // Return the columns list to the caller - resp := com.LiveDBColumnsResponse{Node: com.Conf.Live.Nodename, Columns: columns, PkColumns: pk, Error: "", ErrCode: com.AMQPNoError} - err = com.MQResponse("COLUMNS", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP columns list response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "delete": - // Delete the database file on the node - err = removeLiveDB(req.DBOwner, req.DBName) - if err != nil { - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} - err = com.MQResponse("DELETE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [DELETE] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return a success message (empty string in this case) to the caller - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: ""} - err = com.MQResponse("DELETE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP delete database response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "execute": - // Execute a SQL statement on the database file - var rowsChanged int - rowsChanged, err = com.SQLiteExecuteQueryLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) - if err != nil { - resp := com.LiveDBExecuteResponse{Node: com.Conf.Live.Nodename, RowsChanged: 0, Error: err.Error()} - err = com.MQResponse("EXECUTE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [EXECUTE] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) - } - - // Return a success message to the caller - resp := com.LiveDBExecuteResponse{Node: com.Conf.Live.Nodename, RowsChanged: rowsChanged, Error: ""} - err = com.MQResponse("EXECUTE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP execute query response: '%s'", com.Conf.Live.Nodename, err) - } - continue - case "indexes": - var indexes []com.APIJSONIndex - indexes, err = com.SQLiteGetIndexesLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) - if err != nil { - resp := com.LiveDBIndexesResponse{Node: com.Conf.Live.Nodename, Indexes: []com.APIJSONIndex{}, Error: err.Error()} - err = com.MQResponse("INDEXES", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [INDEXES] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return the indexes list to the caller - resp := com.LiveDBIndexesResponse{Node: com.Conf.Live.Nodename, Indexes: indexes, Error: ""} - err = com.MQResponse("INDEXES", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP indexes list response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "query": - var rows com.SQLiteRecordSet - rows, err = com.SQLiteRunQueryLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) - if err != nil { - resp := com.LiveDBQueryResponse{Node: com.Conf.Live.Nodename, Results: com.SQLiteRecordSet{}, Error: err.Error()} - err = com.MQResponse("QUERY", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [QUERY] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) - } - - // Return the query response to the caller - resp := com.LiveDBQueryResponse{Node: com.Conf.Live.Nodename, Results: rows, Error: ""} - err = com.MQResponse("QUERY", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP query response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "rowdata": - // Extract the request information - // FIXME: Add type checks for safety instead of blind coercing - reqData := req.Data.(map[string]interface{}) - dbTable := reqData["db_table"].(string) - sortCol := reqData["sort_col"].(string) - sortDir := reqData["sort_dir"].(string) - commitID := reqData["commit_id"].(string) - maxRows := int(reqData["max_rows"].(float64)) - rowOffset := int(reqData["row_offset"].(float64)) - - // Open the SQLite database and read the row data - resp := com.LiveDBRowsResponse{Node: com.Conf.Live.Nodename, RowData: com.SQLiteRecordSet{}} - resp.Tables, resp.DefaultTable, resp.RowData, resp.DatabaseSize, err = - com.SQLiteReadDatabasePage("", "", req.RequestingUser, req.DBOwner, req.DBName, dbTable, sortCol, sortDir, commitID, rowOffset, maxRows, true) - if err != nil { - resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} - err = com.MQResponse("ROWDATA", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [ROWDATA] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return the row data to the caller - err = com.MQResponse("ROWDATA", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP query response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "size": - dbPath := filepath.Join(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, "live.sqlite") - var db os.FileInfo - db, err = os.Stat(dbPath) - if err != nil { - resp := com.LiveDBSizeResponse{Node: com.Conf.Live.Nodename, Size: 0, Error: err.Error()} - err = com.MQResponse("SIZE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [SIZE] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return the database size to the caller - resp := com.LiveDBSizeResponse{Node: com.Conf.Live.Nodename, Size: db.Size(), Error: ""} - err = com.MQResponse("SIZE", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP size response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "tables": - var tables []string - tables, err = com.SQLiteGetTablesLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) - if err != nil { - resp := com.LiveDBTablesResponse{Node: com.Conf.Live.Nodename, Tables: nil, Error: err.Error()} - err = com.MQResponse("TABLES", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [TABLES] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return the tables list to the caller - resp := com.LiveDBTablesResponse{Node: com.Conf.Live.Nodename, Tables: tables, Error: ""} - err = com.MQResponse("TABLES", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP tables list response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - case "views": - var views []string - views, err = com.SQLiteGetViewsLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) - if err != nil { - resp := com.LiveDBViewsResponse{Node: com.Conf.Live.Nodename, Views: nil, Error: err.Error()} - err = com.MQResponse("VIEWS", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) - } - continue - } - - if com.AmqpDebug > 0 { - log.Printf("Running [VIEWS] on '%s/%s'", req.DBOwner, req.DBName) - } - - // Return the views list to the caller - resp := com.LiveDBViewsResponse{Node: com.Conf.Live.Nodename, Views: views, Error: ""} - err = com.MQResponse("VIEWS", msg, ch, com.Conf.Live.Nodename, resp) - if err != nil { - log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP views list response: '%s'", com.Conf.Live.Nodename, err) - } - continue - - default: - log.Printf("'%s' received unknown '%s' request on this queue for %s/%s", com.Conf.Live.Nodename, req.Operation, req.DBOwner, req.DBName) - } - } - }() - - log.Printf("Live server '%s' listening for requests", com.Conf.Live.Nodename) + // Launch go workers to process submitted jobs + go com.JobQueueCheck() + go com.JobQueueListen() + + // Launch goroutine event generator for checking submitted jobs + // TODO: This seems to work fine, but is kind of a pita to have enabled while developing this code atm. So we disable it for now. + // TODO: Instead of this, should we run some code on startup of the live nodes that checks the database for + // (recent) unhandled requests, and automatically generates a JobQueueCheck() event if some are found? + //go func() { + // for { + // // Tell the JobQueueCheck() goroutine to check for newly submitted jobs + // com.CheckJobQueue <- struct{}{} + // + // // Wait a second before the next check + // time.Sleep(1 * time.Second) + // } + //}() + + log.Printf("%s: listening for requests", com.Conf.Live.Nodename) // Endless loop var forever chan struct{} <-forever - - // Close the channel to the MQ server - _ = com.CloseMQChannel(ch) -} - -// RemoveLiveDB deletes a live database from the local node. For example, when the user deletes it from -// their account. -// Be aware, it leaves the database owners directory in place, to avoid any potential race condition of -// trying to delete that directory while other databases in their account are being worked with -func removeLiveDB(dbOwner, dbName string) (err error) { - // Get the path to the database file, and it's containing directory - dbDir := filepath.Join(com.Conf.Live.StorageDir, dbOwner, dbName) - dbPath := filepath.Join(dbDir, "live.sqlite") - if _, err = os.Stat(dbPath); err != nil { - if errors.Is(err, fs.ErrNotExist) { - if com.AmqpDebug > 0 { - log.Printf("Live node '%s': database file '%s/%s' was supposed to get deleted here, but was "+ - "missing from filesystem path: '%s'", com.Conf.Live.Nodename, dbOwner, dbName, dbPath) - } - return - } - - // Something wrong with the database file - log.Println(err) - return - } - - // Delete the "live.sqlite" file - // NOTE: If this seems to leave wal or other files hanging around in actual production use, we could - // instead use filepath.RemoveAll(dbDir). That should kill the containing directory and - // all files within, thus not leave anything hanging around - err = os.Remove(dbPath) - if err != nil { - log.Println(err) - return - } - - // Remove the containing directory - err = os.Remove(dbDir) - if err != nil { - log.Println(err) - return - } - - if com.AmqpDebug > 0 { - log.Printf("Live node '%s': Database file '%s/%s' removed from filesystem path: '%s'", - com.Conf.Live.Nodename, dbOwner, dbName, dbPath) - } - return } diff --git a/package.json b/package.json index c728f3dc3..358fe3149 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,7 @@ "docker:start": "docker run -itd --rm --name dbhub-build -p 9443-9445:9443-9445/tcp -p 5550:5550/tcp dbhub-build:latest", "docker:startlocal": "docker run -itd --rm --name dbhub-build --net host --mount type=bind,src=\"$(pwd)\",target=/dbhub.io dbhub-build:latest", "docker:stop": "docker container stop dbhub-build", - "docker:tail": "docker exec -it dbhub-build tail -F /home/dbhub/output.log", - "mq:bind": "docker exec -it dbhub-build rabbitmqctl list_bindings", - "mq:conn": "docker exec -it dbhub-build rabbitmqctl list_connections", - "mq:cons": "docker exec -it dbhub-build rabbitmqctl list_consumers", - "mq:ex": "docker exec -it dbhub-build rabbitmqctl list_exchanges", - "mq:q": "docker exec -it dbhub-build rabbitmqctl list_queues" + "docker:tail": "docker exec -it dbhub-build tail -F /home/dbhub/output.log" }, "engines": { "node": "^20.10.0", diff --git a/standalone/analysis/main.go b/standalone/analysis/main.go index ac6fd1619..44139f3c0 100644 --- a/standalone/analysis/main.go +++ b/standalone/analysis/main.go @@ -39,9 +39,9 @@ func main() { log.Fatal(err) } - // Connect to MQ server + // Connect to job queue server com.Conf.Live.Nodename = "Usage Analysis" - com.AmqpChan, err = com.ConnectMQ() + err = com.ConnectQueue() if err != nil { log.Fatal(err) } @@ -113,7 +113,7 @@ func main() { return } - // Ask our AMQP backend for the database size + // Ask our job queue backend for the database size z, err := com.LiveSize(liveNode, user, user, db.Database) if err != nil { log.Fatal(err) diff --git a/webui/execute.go b/webui/execute.go index 1518631aa..fc83ddb60 100644 --- a/webui/execute.go +++ b/webui/execute.go @@ -61,7 +61,7 @@ func executePage(w http.ResponseWriter, r *http.Request) { return } - // Ask the AMQP backend for the database file size + // Ask the job queue backend for the database file size pageData.DB.Info.DBEntry.Size, err = com.LiveSize(liveNode, pageData.PageMeta.LoggedInUser, dbName.Owner, dbName.Database) if err != nil { errorPage(w, r, http.StatusInternalServerError, err.Error()) @@ -212,7 +212,7 @@ func execLiveSQL(w http.ResponseWriter, r *http.Request) { } } - // Send the SQL execution request to our AMQP backend + // Send the SQL execution request to our job queue backend var z interface{} rowsChanged, err := com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql) if err != nil { diff --git a/webui/main.go b/webui/main.go index d7000ebc2..d5897d130 100644 --- a/webui/main.go +++ b/webui/main.go @@ -2171,7 +2171,7 @@ func deleteDataHandler(w http.ResponseWriter, r *http.Request) { } sql = strings.TrimSuffix(sql, " AND ") - // Send an SQL execution request to our AMQP backend + // Send a SQL execution request to our job queue backend rowsChanged, err := com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql) if err != nil { log.Println(err) @@ -2267,7 +2267,7 @@ func deleteDatabaseHandler(w http.ResponseWriter, r *http.Request) { } } - // For a live database, delete it from both Minio and our AMQP backend + // For a live database, delete it from both Minio and our job queue backend if isLive { // Get the Minio bucket name and object id var bucket, objectID string @@ -2288,7 +2288,7 @@ func deleteDatabaseHandler(w http.ResponseWriter, r *http.Request) { return } - // Delete the database from our AMQP backend + // Delete the database from our job queue backend err = com.LiveDelete(liveNode, loggedInUser, dbOwner, dbName) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -3014,7 +3014,7 @@ func insertDataHandler(w http.ResponseWriter, r *http.Request) { // Produce an insert statement which attempts to insert a new record with default values sql := "INSERT INTO " + com.EscapeId(table) + " DEFAULT VALUES" - // Send an SQL execution request to our AMQP backend + // Send a SQL execution request to our job queue backend rowsChanged, err := com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql) if err != nil { log.Println(err) @@ -3113,6 +3113,9 @@ func main() { log.Fatalf("Configuration file problem: '%s'", err) } + // Set the node name used in various logging strings + com.Conf.Live.Nodename = "WebUI server" + // Set the temp dir environment variable err = os.Setenv("TMPDIR", com.Conf.DiskCache.Directory) if err != nil { @@ -3130,7 +3133,7 @@ func main() { log.Fatalf("Error when opening request log: %s", err) } defer reqLog.Close() - log.Printf("Request log opened: %s", com.Conf.Web.RequestLog) + log.Printf("%s: request log opened: %s", com.Conf.Live.Nodename, com.Conf.Web.RequestLog) // Parse our template files tmpl = template.Must(template.New("templates").Delims("[[", "]]").ParseGlob( @@ -3160,9 +3163,8 @@ func main() { log.Fatal(err) } - // Connect to MQ server - com.Conf.Live.Nodename = "WebUI server" - com.AmqpChan, err = com.ConnectMQ() + // Connect to job queue server + err = com.ConnectQueue() if err != nil { log.Fatal(err) } @@ -3185,6 +3187,13 @@ func main() { // Start the email sending goroutine in the background go com.SendEmails() + // Start background goroutines to handle job queue responses + com.ResponseWaiters = com.NewResponseReceiver() + com.CheckResponsesQueue = make(chan struct{}) + com.SubmitterInstance = com.RandomString(3) + go com.ResponseQueueCheck() + go com.ResponseQueueListen() + // Our pages http.Handle("/", gz.GzipHandler(logReq(mainHandler))) http.Handle("/about", gz.GzipHandler(logReq(aboutPage))) @@ -3479,7 +3488,7 @@ func main() { }))) // Start webUI server - log.Printf("WebUI server starting on https://%s", com.Conf.Web.ServerName) + log.Printf("%s: listening on https://%s", com.Conf.Live.Nodename, com.Conf.Web.ServerName) srv := &http.Server{ Addr: com.Conf.Web.BindAddress, ErrorLog: com.HttpErrorLog(), @@ -3494,11 +3503,6 @@ func main() { if err != nil { log.Println(err) } - - err = com.CloseMQChannel(com.AmqpChan) - if err != nil { - log.Fatal(err) - } } func mainHandler(w http.ResponseWriter, r *http.Request) { @@ -4704,8 +4708,8 @@ func tableViewHandler(w http.ResponseWriter, r *http.Request) { } } } else { - // It's a live database, so we send the request to our AMQP backend - reqData := com.LiveDBRowsRequest{ + // It's a live database, so we send the request to our job queue backend + reqData := com.JobRequestRows{ DbTable: requestedTable, SortCol: sortCol, SortDir: sortDir, @@ -5063,7 +5067,7 @@ func updateDataHandler(w http.ResponseWriter, r *http.Request) { } sql = strings.TrimSuffix(sql, " AND ") - // Send an SQL execution request to our AMQP backend + // Send a SQL execution request to our job queue backend rowsChanged, err := com.LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql) if err != nil { log.Println(err) @@ -5672,8 +5676,8 @@ func uploadDataHandler(w http.ResponseWriter, r *http.Request) { log.Printf("%s: Username: '%s', LIVE database '%s/%s' uploaded', bytes: %v", pageName, loggedInUser, com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), numBytes) - // Send a request to the AMQP backend to set up the database there, ready for querying - liveNode, err := com.LiveCreateDB(com.AmqpChan, dbOwner, dbName, objectID) + // Send a request to the job queue to set up the database + liveNode, err := com.LiveCreateDB(dbOwner, dbName, objectID) if err != nil { log.Println(err) errorPage(w, r, http.StatusInternalServerError, err.Error()) diff --git a/webui/pages.go b/webui/pages.go index dbd21e16d..174d6ce02 100644 --- a/webui/pages.go +++ b/webui/pages.go @@ -849,7 +849,7 @@ func databasePage(w http.ResponseWriter, r *http.Request, dbOwner string, dbName return } - // For non-live databases, add branch, table and view information by querying it directly, otherwise we get the details from our AMQP backend + // For non-live databases, add branch, table and view information by querying it directly, otherwise we get the details from our job queue backend if !pageData.DB.Info.IsLive { // Retrieve default branch name details if pageData.DB.Info.Branch == "" { @@ -1867,7 +1867,7 @@ func settingsPage(w http.ResponseWriter, r *http.Request) { return } - // If it's a standard database then we query it directly, otherwise we query it via our AMQP backend + // If it's a standard database then we query it directly, otherwise we query it via our job queue backend if !pageData.DB.Info.IsLive { // Get a handle from Minio for the database object bkt := pageData.DB.Info.DBEntry.Sha256[:com.MinioFolderChars] @@ -1940,7 +1940,7 @@ func settingsPage(w http.ResponseWriter, r *http.Request) { return } - // Also request the database size from our AMQP backend + // Also request the database size from our job queue backend pageData.DB.Info.DBEntry.Size, err = com.LiveSize(pageData.DB.Info.LiveNode, pageData.PageMeta.LoggedInUser, dbName.Owner, dbName.Database) if err != nil { log.Println(err) diff --git a/webui/vis.go b/webui/vis.go index f68201add..d13eed421 100644 --- a/webui/vis.go +++ b/webui/vis.go @@ -181,7 +181,7 @@ func visualisePage(w http.ResponseWriter, r *http.Request) { return } - // For live databases, we ask the AMQP backend for its file size + // For live databases, we ask the job queue backend for its file size if isLive { pageData.DB.Info.DBEntry.Size, err = com.LiveSize(liveNode, pageData.PageMeta.LoggedInUser, dbName.Owner, dbName.Database) if err != nil { From b884f0b0cbcfee42118f4e842c7496f1c2e31691 Mon Sep 17 00:00:00 2001 From: Justin Clift Date: Mon, 11 Dec 2023 12:11:39 +1000 Subject: [PATCH 2/2] Add back the AMQP code (hopefully temporarily) It's likely safer for our job queue migration to have the DBHub.io daemons be able to run with either job queue system. That way if our new job queue code has problems we can revert to using AMQP until the problems are fixed. With this commit the daemons can run in either AMQP mode or job queue server mode. There's a hard coded boolean value near the top of common/live_amqp.go to switch between them. The Cypress tests pass in both modes, so things should be ok. --- api/handlers.go | 2 +- api/main.go | 14 +- common/config_types.go | 10 + common/cypress.go | 2 +- common/live.go | 638 +++++++++++++----- common/live_amqp.go | 183 +++++ common/live_types.go | 98 ++- .../000005_job_submission_tables.down.sql | 13 +- .../000005_job_submission_tables.up.sql | 21 + docker/Dockerfile | 25 +- docker/config.toml | 8 + go.mod | 1 + go.sum | 8 + live/main.go | 372 +++++++++- package.json | 7 +- standalone/analysis/main.go | 2 +- webui/main.go | 21 +- 17 files changed, 1247 insertions(+), 178 deletions(-) create mode 100644 common/live_amqp.go diff --git a/api/handlers.go b/api/handlers.go index 9908bf3e5..e55685e06 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -1340,7 +1340,7 @@ func uploadHandler(w http.ResponseWriter, r *http.Request) { com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), numBytes) // Send a request to the job queue to set up the database - liveNode, err := com.LiveCreateDB(dbOwner, dbName, objectID) + liveNode, err := com.LiveCreateDB(com.AmqpChan, dbOwner, dbName, objectID) if err != nil { log.Println(err) jsonErr(w, err.Error(), http.StatusInternalServerError) diff --git a/api/main.go b/api/main.go index 3d681e61f..7b5c939d5 100644 --- a/api/main.go +++ b/api/main.go @@ -74,7 +74,7 @@ func main() { } // Connect to job queue server - err = com.ConnectQueue() + com.AmqpChan, err = com.ConnectQueue() if err != nil { log.Fatal(err) } @@ -98,11 +98,13 @@ func main() { } // Start background goroutines to handle job queue responses - com.ResponseWaiters = com.NewResponseReceiver() - com.CheckResponsesQueue = make(chan struct{}) - com.SubmitterInstance = com.RandomString(3) - go com.ResponseQueueCheck() - go com.ResponseQueueListen() + if !com.UseAMQP { + com.ResponseWaiters = com.NewResponseReceiver() + com.CheckResponsesQueue = make(chan struct{}) + com.SubmitterInstance = com.RandomString(3) + go com.ResponseQueueCheck() + go com.ResponseQueueListen() + } // Load our self signed CA chain ourCAPool = x509.NewCertPool() diff --git a/common/config_types.go b/common/config_types.go index d68a51156..f249b0c73 100644 --- a/common/config_types.go +++ b/common/config_types.go @@ -14,6 +14,7 @@ type TomlConfig struct { Live LiveInfo Memcache MemcacheInfo Minio MinioInfo + MQ MQInfo Pg PGInfo Sign SigningInfo UserMgmt UserMgmtInfo @@ -92,6 +93,15 @@ type MinioInfo struct { Server string } +type MQInfo struct { + CertFile string `toml:"cert_file"` + KeyFile string `toml:"key_file"` + Password string `toml:"password"` + Port int `toml:"port"` + Server string `toml:"server"` + Username string `toml:"username"` +} + // PGInfo contains the PostgreSQL connection parameters type PGInfo struct { Database string diff --git a/common/cypress.go b/common/cypress.go index 53eb81b52..37784bb58 100644 --- a/common/cypress.go +++ b/common/cypress.go @@ -87,7 +87,7 @@ func CypressSeed(w http.ResponseWriter, r *http.Request) { // Send the live database file to our job queue backend for setup dbOwner := "default" dbName := "Join Testing with index.sqlite" - liveNode, err := LiveCreateDB(dbOwner, dbName, objectID) + liveNode, err := LiveCreateDB(AmqpChan, dbOwner, dbName, objectID) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/common/live.go b/common/live.go index 84e9afbbd..e475c6132 100644 --- a/common/live.go +++ b/common/live.go @@ -2,6 +2,7 @@ package common import ( "context" + "crypto/tls" "encoding/json" "errors" "fmt" @@ -16,6 +17,7 @@ import ( sqlite "github.com/gwenn/gosqlite" "github.com/jackc/pgx/v5" pgpool "github.com/jackc/pgx/v5/pgxpool" + amqp "github.com/rabbitmq/amqp091-go" ) const ( @@ -35,117 +37,289 @@ var ( ) // ConnectQueue creates the connections to the backend queue server -func ConnectQueue() (err error) { - // Connect to PostgreSQL based queue server - // Note: JobListenConn uses a dedicated, non-pooled connection to the job queue database, while JobQueueConn uses - // a standard database connection pool - JobListenConn, err = pgx.ConnectConfig(context.Background(), listenConfig) - if err != nil { - return fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) - } - JobQueueConn, err = pgpool.New(context.Background(), pgConfig.ConnString()) - if err != nil { - return fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) +func ConnectQueue() (channel *amqp.Channel, err error) { + if UseAMQP { + // AMQP only + var conn *amqp.Connection + if Conf.Environment.Environment == "production" { + // If certificate/key files have been provided, then we can use mutual TLS (mTLS) + if Conf.MQ.CertFile != "" && Conf.MQ.KeyFile != "" { + var cert tls.Certificate + cert, err = tls.LoadX509KeyPair(Conf.MQ.CertFile, Conf.MQ.KeyFile) + if err != nil { + return + } + cfg := &tls.Config{Certificates: []tls.Certificate{cert}} + conn, err = amqp.DialTLS(fmt.Sprintf("amqps://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port), cfg) + if err != nil { + return + } + log.Printf("%s connected to AMQP server using mutual TLS (mTLS): %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) + } else { + // Fallback to just verifying the server certs for TLS. This is needed by the DB4S end point, as it + // uses certs from our own CA, so mTLS won't easily work with it. + conn, err = amqp.Dial(fmt.Sprintf("amqps://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port)) + if err != nil { + return + } + log.Printf("%s connected to AMQP server with server-only TLS: %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) + } + } else { + // Everywhere else (eg docker container) doesn't *have* to use TLS + conn, err = amqp.Dial(fmt.Sprintf("amqp://%s:%s@%s:%d/", Conf.MQ.Username, Conf.MQ.Password, Conf.MQ.Server, Conf.MQ.Port)) + if err != nil { + return + } + log.Printf("%s connected to AMQP server without encryption: %v:%d", Conf.Live.Nodename, Conf.MQ.Server, Conf.MQ.Port) + } + + channel, err = conn.Channel() + } else { + // Connect to PostgreSQL based queue server + // Note: JobListenConn uses a dedicated, non-pooled connection to the job queue database, while JobQueueConn uses + // a standard database connection pool + JobListenConn, err = pgx.ConnectConfig(context.Background(), listenConfig) + if err != nil { + return nil, fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) + } + JobQueueConn, err = pgpool.New(context.Background(), pgConfig.ConnString()) + if err != nil { + return nil, fmt.Errorf("%s: couldn't connect to backend queue server: %v", Conf.Live.Nodename, err) + } } return } // LiveBackup asks the job queue backend to store the given database back into Minio func LiveBackup(liveNode, loggedInUser, dbOwner, dbName string) (err error) { - // Send the backup request to our job queue backend - var resp JobResponseDBError - err = JobSubmit(&resp, liveNode, "backup", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "backup", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } + + // Decode the response + var resp LiveDBErrorResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + + // If the backup failed, then provide the error message to the user + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'backup' request, but didn't identify itself.") + return + } + } else { + // Send the backup request to our job queue backend + var resp JobResponseDBError + err = JobSubmit(&resp, liveNode, "backup", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - log.Printf("%s: node which handled the database backup request: %s", Conf.Live.Nodename, liveNode) + log.Printf("%s: node which handled the database backup request: %s", Conf.Live.Nodename, liveNode) - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned during database backup on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database backup on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) + } } return } // LiveColumns requests the job queue backend to return a list of all columns of the given table func LiveColumns(liveNode, loggedInUser, dbOwner, dbName, table string) (columns []sqlite.Column, pk []string, err error) { - // Send the column list request to our job queue backend - var resp JobResponseDBColumns - err = JobSubmit(&resp, liveNode, "columns", loggedInUser, dbOwner, dbName, table) - if err != nil { - return - } + if UseAMQP { + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "columns", loggedInUser, dbOwner, dbName, table) + if err != nil { + return + } - // Return the requested data - columns = resp.Columns - pk = resp.PkColumns + // Decode the response + var resp LiveDBColumnsResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'columns' request, but didn't identify itself.") + return + } + columns = resp.Columns + pk = resp.PkColumns + } else { + // Send the column list request to our job queue backend + var resp JobResponseDBColumns + err = JobSubmit(&resp, liveNode, "columns", loggedInUser, dbOwner, dbName, table) + if err != nil { + return + } + + // Return the requested data + columns = resp.Columns + pk = resp.PkColumns - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the column list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the column list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } // LiveCreateDB requests the job queue backend create a new live SQLite database -func LiveCreateDB(dbOwner, dbName, objectID string) (liveNode string, err error) { - // Send the database setup request to our job queue backend - var resp JobResponseDBCreate - err = JobSubmit(&resp, "any", "createdb", "", dbOwner, dbName, objectID) - if err != nil { - return - } +func LiveCreateDB(channel *amqp.Channel, dbOwner, dbName, objectID string) (liveNode string, err error) { + if UseAMQP { + // Send the database setup request to our AMQP backend + var rawResponse []byte + rawResponse, err = MQRequest(channel, "create_queue", "createdb", "", dbOwner, dbName, objectID) + if err != nil { + return + } - // Return the name of the node which has the database - liveNode = resp.NodeName + // Decode the response + var resp LiveDBResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + log.Println(err) + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'create' request, but didn't identify itself.") + return + } + if resp.Result != "success" { + err = errors.New(fmt.Sprintf("LIVE database (%s/%s) creation apparently didn't fail, but the response didn't include a success message", + dbOwner, dbName)) + return + } - log.Printf("%s: node which handled the database creation request: %s", Conf.Live.Nodename, liveNode) + // Return the name of the node which has the database + liveNode = resp.Node - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned during database creation on '%s': '%v'", Conf.Live.Nodename, resp.NodeName, resp.Err) + } else { + // Send the database setup request to our job queue backend + var resp JobResponseDBCreate + err = JobSubmit(&resp, "any", "createdb", "", dbOwner, dbName, objectID) + if err != nil { + return + } + + // Return the name of the node which has the database + liveNode = resp.NodeName + + log.Printf("%s: node which handled the database creation request: %s", Conf.Live.Nodename, liveNode) + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database creation on '%s': '%v'", Conf.Live.Nodename, resp.NodeName, resp.Err) + } } return } // LiveDelete asks our job queue backend to delete a database func LiveDelete(liveNode, loggedInUser, dbOwner, dbName string) (err error) { - // Send the database setup request to our job queue backend - var resp JobResponseDBError - err = JobSubmit(&resp, liveNode, "delete", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + // Delete the database from our AMQP backend + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "delete", loggedInUser, dbOwner, dbName, "") + if err != nil { + log.Println(err) + return + } + + // Decode the response + var resp LiveDBErrorResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + log.Println(err) + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'delete' request, but didn't identify itself.") + return + } + } else { + // Send the database setup request to our job queue backend + var resp JobResponseDBError + err = JobSubmit(&resp, liveNode, "delete", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned during database deletion on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned during database deletion on '%s': '%v'", Conf.Live.Nodename, liveNode, resp.Err) + } } return } // LiveExecute asks our job queue backend to execute a SQL statement on a database func LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql string) (rowsChanged int, err error) { - // Send the execute request to our job queue backend - var resp JobResponseDBExecute - err = JobSubmit(&resp, liveNode, "execute", loggedInUser, dbOwner, dbName, sql) - if err != nil { - return - } + if UseAMQP { + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "execute", loggedInUser, dbOwner, dbName, sql) + if err != nil { + return + } - // Return the number of rows changed by the execution run - rowsChanged = resp.RowsChanged + // Decode the response + var resp LiveDBExecuteResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + log.Println(err) + return + } - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - if !strings.HasPrefix(err.Error(), "don't use exec with") { - log.Printf("%s: an error was returned when retrieving the execution result for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // If the SQL execution failed, then provide the error message to the user + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + rowsChanged = resp.RowsChanged + + } else { + // Send the execute request to our job queue backend + var resp JobResponseDBExecute + err = JobSubmit(&resp, liveNode, "execute", loggedInUser, dbOwner, dbName, sql) + if err != nil { + return + } + + // Return the number of rows changed by the execution run + rowsChanged = resp.RowsChanged + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + if !strings.HasPrefix(err.Error(), "don't use exec with") { + log.Printf("%s: an error was returned when retrieving the execution result for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } } return @@ -153,108 +327,244 @@ func LiveExecute(liveNode, loggedInUser, dbOwner, dbName, sql string) (rowsChang // LiveIndexes asks our job queue backend to provide the list of indexes in a database func LiveIndexes(liveNode, loggedInUser, dbOwner, dbName string) (indexes []APIJSONIndex, err error) { - // Send the index request to our job queue backend - var resp JobResponseDBIndexes - err = JobSubmit(&resp, liveNode, "indexes", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + // Send the index request to our job queue backend + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "indexes", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } + + // Decode the response + var resp LiveDBIndexesResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'indexes' request, but didn't identify itself.") + return + } + // Return the index list for the live database + indexes = resp.Indexes + + } else { + // Send the index request to our job queue backend + var resp JobResponseDBIndexes + err = JobSubmit(&resp, liveNode, "indexes", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Return the index list for the live database - indexes = resp.Indexes + // Return the index list for the live database + indexes = resp.Indexes - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the index list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the index list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } // LiveQuery sends a SQLite query to a live database on its hosting node func LiveQuery(liveNode, loggedInUser, dbOwner, dbName, query string) (rows SQLiteRecordSet, err error) { - // Send the query to our job queue backend - var resp JobResponseDBQuery - err = JobSubmit(&resp, liveNode, "query", loggedInUser, dbOwner, dbName, query) - if err != nil { - return - } + if UseAMQP { + // Send the query request to our AMQP backend + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "query", loggedInUser, dbOwner, dbName, query) + if err != nil { + return + } + + // Decode the response + var resp LiveDBQueryResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + log.Println(err) + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + // Return the query response + rows = resp.Results + + } else { + // Send the query to our job queue backend + var resp JobResponseDBQuery + err = JobSubmit(&resp, liveNode, "query", loggedInUser, dbOwner, dbName, query) + if err != nil { + return + } - // Return the query response - rows = resp.Results + // Return the query response + rows = resp.Results - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the query response for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the query response for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } // LiveRowData asks our job queue backend to send us the SQLite table data for a given range of rows func LiveRowData(liveNode, loggedInUser, dbOwner, dbName string, reqData JobRequestRows) (rowData SQLiteRecordSet, err error) { - // Serialise the row data request to JSON - // NOTE - This actually causes the serialised field to be stored in PG as base64 instead. Not sure why, but we can work with it. - reqJSON, err := json.Marshal(reqData) - if err != nil { - log.Println(err) - return - } + if UseAMQP { + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqData) + if err != nil { + log.Println(err) + return + } - // Send the row data request to our job queue backend - var resp JobResponseDBRows - err = JobSubmit(&resp, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqJSON) - if err != nil { - return - } + // Decode the response + var resp LiveDBRowsResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + log.Println(err) + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + log.Println(err) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'rowdata' request, but didn't identify itself.") + return + } - // Return the row data for the requested table - rowData = resp.RowData + // Return the row data for the requested table + rowData = resp.RowData + + } else { + // Serialise the row data request to JSON + // NOTE - This actually causes the serialised field to be stored in PG as base64 instead. Not sure why, but we can work with it. + var reqJSON []byte + reqJSON, err = json.Marshal(reqData) + if err != nil { + log.Println(err) + return + } - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the row data for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Send the row data request to our job queue backend + var resp JobResponseDBRows + err = JobSubmit(&resp, liveNode, "rowdata", loggedInUser, dbOwner, dbName, reqJSON) + if err != nil { + return + } + + // Return the row data for the requested table + rowData = resp.RowData + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the row data for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } // LiveSize asks our job queue backend for the file size of a database func LiveSize(liveNode, loggedInUser, dbOwner, dbName string) (size int64, err error) { - // Send the size request to our job queue backend - var resp JobResponseDBSize - err = JobSubmit(&resp, liveNode, "size", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + // Send the size request to our AMQP backend + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "size", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Return the size of the live database - size = resp.Size + // Decode the response + var resp LiveDBSizeResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'size' request, but didn't identify itself.") + return + } + // Return the size of the live database + size = resp.Size + + } else { + // Send the size request to our job queue backend + var resp JobResponseDBSize + err = JobSubmit(&resp, liveNode, "size", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } + + // Return the size of the live database + size = resp.Size - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when checking the on disk database size for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when checking the on disk database size for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } // LiveTables asks our job queue backend to provide the list of tables (not including views!) in a database func LiveTables(liveNode, loggedInUser, dbOwner, dbName string) (tables []string, err error) { - // Send the tables request to our job queue backend - var resp JobResponseDBTables - err = JobSubmit(&resp, liveNode, "tables", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + // Send the tables request to our AMQP backend + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "tables", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } + + // Decode the response + var resp LiveDBTablesResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'tables' request, but didn't identify itself.") + return + } + // Return the table list for the live database + tables = resp.Tables + + } else { + // Send the tables request to our job queue backend + var resp JobResponseDBTables + err = JobSubmit(&resp, liveNode, "tables", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Return the table list for the live database - tables = resp.Tables + // Return the table list for the live database + tables = resp.Tables - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the table list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the table list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } @@ -282,20 +592,46 @@ func LiveTablesAndViews(liveNode, loggedInUser, dbOwner, dbName string) (list [] // LiveViews asks our job queue backend to provide the list of views (not including tables!) in a database func LiveViews(liveNode, loggedInUser, dbOwner, dbName string) (views []string, err error) { - // Send the views request to our job queue backend - var resp JobResponseDBViews - err = JobSubmit(&resp, liveNode, "views", loggedInUser, dbOwner, dbName, "") - if err != nil { - return - } + if UseAMQP { + var rawResponse []byte + rawResponse, err = MQRequest(AmqpChan, liveNode, "views", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Return the view list for the live database - views = resp.Views + // Decode the response + var resp LiveDBViewsResponse + err = json.Unmarshal(rawResponse, &resp) + if err != nil { + return + } + if resp.Error != "" { + err = errors.New(resp.Error) + return + } + if resp.Node == "" { + log.Println("A node responded to a 'views' request, but didn't identify itself.") + return + } + // Return the view list for the live database + views = resp.Views + + } else { + // Send the views request to our job queue backend + var resp JobResponseDBViews + err = JobSubmit(&resp, liveNode, "views", loggedInUser, dbOwner, dbName, "") + if err != nil { + return + } - // Handle error response from the live node - if resp.Err != "" { - err = errors.New(resp.Err) - log.Printf("%s: an error was returned when retrieving the view list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + // Return the view list for the live database + views = resp.Views + + // Handle error response from the live node + if resp.Err != "" { + err = errors.New(resp.Err) + log.Printf("%s: an error was returned when retrieving the view list for '%s/%s': '%v'", Conf.Live.Nodename, dbOwner, dbName, resp.Err) + } } return } diff --git a/common/live_amqp.go b/common/live_amqp.go new file mode 100644 index 000000000..4ed9a35d9 --- /dev/null +++ b/common/live_amqp.go @@ -0,0 +1,183 @@ +package common + +import ( + "context" + "encoding/json" + "fmt" + "log" + + amqp "github.com/rabbitmq/amqp091-go" +) + +var ( + // AmqpChan is the AMQP channel handle we use for communication with our AMQP backend + AmqpChan *amqp.Channel + + // UseAMQP switches between running in AMQP mode (true) or job queue server mode (false) + UseAMQP = true +) + +// CloseMQChannel closes an open AMQP channel +func CloseMQChannel(channel *amqp.Channel) (err error) { + err = channel.Close() + return +} + +// CloseMQConnection closes an open AMQP connection +func CloseMQConnection(connection *amqp.Connection) (err error) { + err = connection.Close() + return +} + +// MQResponse sends an AMQP response back to its requester +func MQResponse(requestType string, msg amqp.Delivery, channel *amqp.Channel, nodeName string, responseData interface{}) (err error) { + var z []byte + z, err = json.Marshal(responseData) + if err != nil { + log.Println(err) + // It's super unlikely we can safely return here without ack-ing the message. So as something has gone + // wrong with json.Marshall() we'd better just attempt passing back info about that error message instead (!) + z = []byte(fmt.Sprintf(`{"node":"%s","error":"%s"}`, nodeName, err.Error())) // This is a LiveDBErrorResponse structure + } + + // Send the message + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) + defer cancel() + err = channel.PublishWithContext(ctx, "", msg.ReplyTo, false, false, + amqp.Publishing{ + ContentType: "text/json", + CorrelationId: msg.CorrelationId, + Body: z, + }) + if err != nil { + log.Println(err) + } + + // Acknowledge the request, so it doesn't stick around in the queue + err = msg.Ack(false) + if err != nil { + log.Println(err) + } + + if JobQueueDebug > 0 { + log.Printf("[%s] Live node '%s' responded with ACK to message with correlationID: '%s', msg.ReplyTo: '%s'", requestType, nodeName, msg.CorrelationId, msg.ReplyTo) + return + } + return +} + +// MQCreateDBQueue creates a queue on the MQ server for "create database" messages +func MQCreateDBQueue(channel *amqp.Channel) (queue amqp.Queue, err error) { + queue, err = channel.QueueDeclare("create_queue", true, false, false, false, nil) + if err != nil { + return + } + + // FIXME: Re-read the docs for this, and work out if this is needed + err = channel.Qos(1, 0, false) + if err != nil { + return + } + return +} + +// MQCreateQueryQueue creates a queue on the MQ server for sending database queries to +func MQCreateQueryQueue(channel *amqp.Channel, nodeName string) (queue amqp.Queue, err error) { + queue, err = channel.QueueDeclare(nodeName, false, false, false, false, nil) + if err != nil { + return + } + + // FIXME: Re-read the docs for this, and work out if this is needed + err = channel.Qos(0, 0, false) + if err != nil { + return + } + return +} + +// MQCreateResponse sends a success/failure response back +func MQCreateResponse(msg amqp.Delivery, channel *amqp.Channel, nodeName, result string) (err error) { + // Construct the response. It's such a simple string we just create it directly instead of using json.Marshall() + resp := fmt.Sprintf(`{"node":"%s","dbowner":"","dbname":"","result":"%s","error":""}`, nodeName, result) + + // Send the message + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) + defer cancel() + err = channel.PublishWithContext(ctx, "", msg.ReplyTo, false, false, + amqp.Publishing{ + ContentType: "text/json", + CorrelationId: msg.CorrelationId, + Body: []byte(resp), + }) + if err != nil { + log.Println(err) + } + msg.Ack(false) + if JobQueueDebug > 0 { + log.Printf("[CREATE] Live node '%s' responded with ACK to message with correlationID: '%s', msg.ReplyTo: '%s'", nodeName, msg.CorrelationId, msg.ReplyTo) + } + return +} + +// MQRequest is the main function used for sending requests to our AMQP backend +func MQRequest(channel *amqp.Channel, queue, operation, requestingUser, dbOwner, dbName string, data interface{}) (result []byte, err error) { + // Create a temporary AMQP queue for receiving the response + var q amqp.Queue + q, err = channel.QueueDeclare("", false, false, true, false, nil) + if err != nil { + return + } + + // Construct the request + bar := LiveDBRequest{ + Operation: operation, + DBOwner: dbOwner, + DBName: dbName, + Data: data, + RequestingUser: requestingUser, + } + var z []byte + z, err = json.Marshal(bar) + if err != nil { + log.Println(err) + return + } + + // Send the request via AMQP + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) + defer cancel() + corrID := RandomString(32) + err = channel.PublishWithContext(ctx, "", queue, false, false, + amqp.Publishing{ + ContentType: "text/json", + CorrelationId: corrID, + ReplyTo: q.Name, + Body: z, + }) + if err != nil { + log.Println(err) + return + } + + // Start processing messages from the AMQP response queue + msgs, err := channel.Consume(q.Name, "", true, false, false, false, nil) + if err != nil { + return + } + + // Wait for, then extract the response. Without json unmarshalling it yet + for d := range msgs { + if corrID == d.CorrelationId { + result = d.Body + break + } + } + + // Delete the temporary queue + _, err = channel.QueueDelete(q.Name, false, false, false) + if err != nil { + log.Println(err) + } + return +} diff --git a/common/live_types.go b/common/live_types.go index df5a65ee8..ceada8db5 100644 --- a/common/live_types.go +++ b/common/live_types.go @@ -102,7 +102,7 @@ type ResponseReceivers struct { receivers map[int]*chan ResponseInfo } -// NewResponseReceiver is the constructor function for correcting creating new ResponseReceivers structures +// NewResponseReceiver is the constructor function for creating new ResponseReceivers func NewResponseReceiver() *ResponseReceivers { z := ResponseReceivers{ Mutex: sync.Mutex{}, @@ -125,3 +125,99 @@ func (r *ResponseReceivers) RemoveReceiver(jobID int) { delete(r.receivers, jobID) r.Unlock() } + +// *** Legacy (hopefully) AMQP related types + +// LiveDBColumnsResponse holds the fields used for receiving column list responses from our AMQP backend +type LiveDBColumnsResponse struct { + Node string `json:"node"` + Columns []sqlite.Column `json:"columns"` + PkColumns []string `json:"pkColuns"` + Error string `json:"error"` + ErrCode JobQueueErrorCode `json:"error_code"` +} + +// LiveDBErrorResponse holds just the node name and any error message used in responses by our AMQP backend +// It's useful for error message, and other responses where no other fields are needed +type LiveDBErrorResponse struct { + Node string `json:"node"` + Error string `json:"error"` +} + +// LiveDBExecuteResponse returns the number of rows changed by an Execute() call +type LiveDBExecuteResponse struct { + Node string `json:"node"` + RowsChanged int `json:"rows_changed"` + Error string `json:"error"` +} + +// LiveDBIndexesResponse holds the fields used for receiving index list responses from our AMQP backend +type LiveDBIndexesResponse struct { + Node string `json:"node"` + Indexes []APIJSONIndex `json:"indexes"` + Error string `json:"error"` +} + +// LiveDBQueryResponse holds the fields used for receiving query responses from our AMQP backend +type LiveDBQueryResponse struct { + Node string `json:"node"` + Results SQLiteRecordSet `json:"results"` + Error string `json:"error"` +} + +// LiveDBRequest holds the fields used for sending requests to our AMQP backend +type LiveDBRequest struct { + Operation string `json:"operation"` + DBOwner string `json:"dbowner"` + DBName string `json:"dbname"` + Data interface{} `json:"data,omitempty"` + RequestingUser string `json:"requesting_user"` +} + +// LiveDBResponse holds the fields used for receiving (non-query) responses from our AMQP backend +type LiveDBResponse struct { + Node string `json:"node"` + Result string `json:"result"` + Error string `json:"error"` +} + +// LiveDBRowsRequest holds the data used when making an AMQP rows request +type LiveDBRowsRequest struct { + DbTable string `json:"db_table"` + SortCol string `json:"sort_col"` + SortDir string `json:"sort_dir"` + CommitID string `json:"commit_id"` + RowOffset int `json:"row_offset"` + MaxRows int `json:"max_rows"` +} + +// LiveDBRowsResponse holds the fields used for receiving database page row responses from our AMQP backend +type LiveDBRowsResponse struct { + Node string `json:"node"` + DatabaseSize int64 `json:"database_size"` + DefaultTable string `json:"default_table"` + Error string `json:"error"` + RowData SQLiteRecordSet `json:"row_data"` + Tables []string `json:"tables"` +} + +// LiveDBSizeResponse holds the fields used for receiving database size responses from our AMQP backend +type LiveDBSizeResponse struct { + Node string `json:"node"` + Size int64 `json:"size"` + Error string `json:"error"` +} + +// LiveDBTablesResponse holds the fields used for receiving table list responses from our AMQP backend +type LiveDBTablesResponse struct { + Node string `json:"node"` + Tables []string `json:"tables"` + Error string `json:"error"` +} + +// LiveDBViewsResponse holds the fields used for receiving view list responses from our AMQP backend +type LiveDBViewsResponse struct { + Node string `json:"node"` + Views []string `json:"views"` + Error string `json:"error"` +} diff --git a/database/migrations/000005_job_submission_tables.down.sql b/database/migrations/000005_job_submission_tables.down.sql index aa959f0ef..0bcc3c4c5 100644 --- a/database/migrations/000005_job_submission_tables.down.sql +++ b/database/migrations/000005_job_submission_tables.down.sql @@ -1,8 +1,15 @@ BEGIN; -DROP TABLE IF EXISTS job_submissions; -DROP TABLE IF EXISTS job_responses; DROP TRIGGER IF EXISTS job_submissions_trigger ON job_submissions; -DROP TRIGGER IF EXISTS job_responses_trigger ON job_responses; DROP FUNCTION IF EXISTS job_submissions_notify(); +DROP INDEX IF EXISTS job_submissions_completed_date_index; +DROP INDEX IF EXISTS job_submissions_state_index; +DROP INDEX IF EXISTS job_submissions_submission_date_index; +DROP INDEX IF EXISTS job_submissions_target_node_index; +DROP TABLE IF EXISTS job_submissions; +DROP TRIGGER IF EXISTS job_responses_trigger ON job_responses; DROP FUNCTION IF EXISTS job_responses_notify(); +DROP INDEX IF EXISTS job_responses_processed_date_index; +DROP INDEX IF EXISTS job_responses_response_date_index; +DROP INDEX IF EXISTS job_responses_submitter_node_index; +DROP TABLE IF EXISTS job_responses; COMMIT; \ No newline at end of file diff --git a/database/migrations/000005_job_submission_tables.up.sql b/database/migrations/000005_job_submission_tables.up.sql index af3b9ff91..0261c26b7 100644 --- a/database/migrations/000005_job_submission_tables.up.sql +++ b/database/migrations/000005_job_submission_tables.up.sql @@ -12,6 +12,18 @@ CREATE TABLE IF NOT EXISTS job_submissions ( completed_date TIMESTAMP WITH TIME ZONE ); +CREATE INDEX job_submissions_completed_date_index + ON job_submissions (completed_date); + +CREATE INDEX job_submissions_state_index + ON job_submissions (state); + +CREATE INDEX job_submissions_submission_date_index + ON job_submissions (submission_date); + +CREATE INDEX job_submissions_target_node_index + ON job_submissions (target_node); + -- job_responses table CREATE TABLE IF NOT EXISTS job_responses ( @@ -27,6 +39,15 @@ CREATE TABLE IF NOT EXISTS job_responses processed_date TIMESTAMP WITH TIME ZONE ); +CREATE INDEX job_responses_processed_date_index + ON job_responses (processed_date); + +CREATE INDEX job_responses_response_date_index + ON job_responses (response_date); + +CREATE INDEX job_responses_submitter_node_index + ON job_responses (submitter_node); + -- notify function for the job_submissions table CREATE OR REPLACE FUNCTION job_submissions_notify() RETURNS trigger AS $$ diff --git a/docker/Dockerfile b/docker/Dockerfile index 9cc79ed6b..73dc359ac 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,10 +1,26 @@ # vim:set ft=dockerfile: +# We grab RabbitMQ and Erlang from the official RabbitMQ image +FROM rabbitmq:3-alpine AS rabbit + # Build our own image FROM alpine:3.19 LABEL maintainer="Justin Clift " +# Copy the RabbitMQ files we need +COPY --from=rabbit /etc/rabbitmq /etc/rabbitmq +COPY --from=rabbit /opt/erlang /opt/erlang +COPY --from=rabbit /opt/openssl /opt/openssl +COPY --from=rabbit /opt/rabbitmq /opt/rabbitmq +COPY --from=rabbit /var/lib/rabbitmq /var/lib/rabbitmq +COPY --from=rabbit /var/log/rabbitmq /var/log/rabbitmq + +# Create the rabbitmq user and group +RUN addgroup -S rabbitmq && \ + adduser -S -D -h /var/lib/rabbitmq -s /sbin/nologin -g "Linux User,,," -G rabbitmq rabbitmq && \ + chown -Rh rabbitmq: /opt/rabbitmq /var/log/rabbitmq + # Use a fast Australian mirror for the Alpine package repositories # Without doing this, building the image can take 2+ hours. :( RUN echo "https://mirror.aarnet.edu.au/pub/alpine/v3.19/main" > /etc/apk/repositories && \ @@ -75,8 +91,13 @@ RUN echo "echo 127.0.0.1 docker-dev.dbhub.io docker-dev >> /etc/hosts" >> /usr/l echo "su - minio -c '/usr/bin/minio server --quiet --anonymous /var/lib/minio/data 2>&1 &'" >> /usr/local/bin/start.sh && \ echo "su - postgres -c '/usr/libexec/postgresql/pg_ctl start'" >> /usr/local/bin/start.sh && \ echo "" >> /usr/local/bin/start.sh && \ - echo "# Delay long enough for the DBHub.io daemons to start" >> /usr/local/bin/start.sh && \ - echo "sleep 1" >> /usr/local/bin/start.sh && \ + echo "unset CONFIG_FILE" >> /usr/local/bin/start.sh && \ + echo "export RABBITMQ_CONFIG_FILES=/etc/rabbitmq/conf.d" >> /usr/local/bin/start.sh && \ + echo "export PATH=/opt/rabbitmq/sbin:/opt/erlang/bin:/opt/openssl/bin:$PATH" >> /usr/local/bin/start.sh && \ + echo "/opt/rabbitmq/sbin/rabbitmq-server &" >> /usr/local/bin/start.sh && \ + echo "" >> /usr/local/bin/start.sh && \ + echo "# Wait for RabbitMQ to start before launching the DBHub.io daemons" >> /usr/local/bin/start.sh && \ + echo "sleep 15" >> /usr/local/bin/start.sh && \ echo "" >> /usr/local/bin/start.sh && \ echo "su - dbhub -c 'if [ -f "${DBHUB_SOURCE}/.env" ]; then source ${DBHUB_SOURCE}/.env; fi; CONFIG_FILE=${CONFIG_FILE} /usr/local/bin/dbhub-webui >>/home/dbhub/output.log 2>&1 &'" >> /usr/local/bin/start.sh && \ echo "su - dbhub -c 'if [ -f "${DBHUB_SOURCE}/.env" ]; then source ${DBHUB_SOURCE}/.env; fi; CONFIG_FILE=${CONFIG_FILE} /usr/local/bin/dbhub-api >>/home/dbhub/output.log 2>&1 &'" >> /usr/local/bin/start.sh && \ diff --git a/docker/config.toml b/docker/config.toml index 420ca9c2a..9640ddca8 100644 --- a/docker/config.toml +++ b/docker/config.toml @@ -45,6 +45,14 @@ access_key = "minio" secret = "minio123" https = false +[mq] +cert_file = "" +key_file = "" +password = "guest" +port = 5672 +server = "localhost" +username = "guest" + [pg] database = "dbhub" num_connections = 5 diff --git a/go.mod b/go.mod index 8217476f9..bea79c343 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/leodido/go-urn v1.2.0 // indirect github.com/memcachier/mc v2.0.1+incompatible // indirect github.com/microcosm-cc/bluemonday v1.0.16 // indirect + github.com/rabbitmq/amqp091-go v1.9.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/russross/blackfriday v2.0.0+incompatible // indirect github.com/sergi/go-diff v1.1.0 // indirect diff --git a/go.sum b/go.sum index ffbd003f6..d5d9545db 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo= +github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -133,12 +135,16 @@ github.com/sqlitebrowser/blackfriday v9.0.0+incompatible/go.mod h1:/zga9sqpWzcew github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 h1:s0+Ea95n1LrsKh6rtclU/9Qb2/5ofvnfnR7gDDiFTw8= github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47/go.mod h1:8vPIKi5FslxCXEgfQxrFtWfdclGy6VWAc9NA1ZTYCJg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= @@ -172,6 +178,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -179,3 +186,4 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/live/main.go b/live/main.go index 2bae81573..a17356e04 100644 --- a/live/main.go +++ b/live/main.go @@ -3,10 +3,14 @@ package main // Internal daemon for running SQLite queries sent by the other DBHub.io daemons import ( + "encoding/json" "errors" + "fmt" "log" "os" + "path/filepath" + sqlite "github.com/gwenn/gosqlite" com "github.com/sqlitebrowser/dbhub.io/common" ) @@ -55,15 +59,370 @@ func main() { } // Connect to the job queue - com.CheckJobQueue = make(chan struct{}) - err = com.ConnectQueue() + if !com.UseAMQP { + com.CheckJobQueue = make(chan struct{}) + } + ch, err := com.ConnectQueue() if err != nil { log.Fatal(err) } + // Make sure the channel to the AMQP server is still open + if com.UseAMQP { + // Create queue for receiving new database creation requests + createQueue, err := com.MQCreateDBQueue(ch) + if err != nil { + log.Fatal(err) + } + + // Start consuming database creation requests + createDBMsgs, err := ch.Consume(createQueue.Name, "", false, false, false, false, nil) + + go func() { + for d := range createDBMsgs { + // Decode JSON request + var req com.LiveDBRequest + err = json.Unmarshal(d.Body, &req) + if err != nil { + log.Println(err) + err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") + if err != nil { + log.Printf("Error: occurred on live node '%s' in the create db code, while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + // Verify that the object ID was passed through the interface correctly + objectID, ok := req.Data.(string) + if !ok { + err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") + if err != nil { + log.Printf("Error: occurred on live node '%s' in the create db code, while converting the Minio object ID to a string: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + // Set up the live database locally + _, err = com.LiveRetrieveDatabaseMinio(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, objectID) + if err != nil { + log.Println(err) + err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "failure") + if err != nil { + log.Printf("Error: occurred on live node '%s' in the create db code, while constructing an AMQP error message response (location 2): '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + // Respond to the creation request with a success message + err = com.MQCreateResponse(d, ch, com.Conf.Live.Nodename, "success") + if err != nil { + continue + } + } + }() + + // Create the queue for receiving database queries + queryQueue, err := com.MQCreateQueryQueue(ch, com.Conf.Live.Nodename) + if err != nil { + log.Fatal(err) + } + + // Start consuming database query requests + requests, err := ch.Consume(queryQueue.Name, "", false, false, false, false, nil) + if err != nil { + log.Fatal(err) + } + go func() { + for msg := range requests { + if com.JobQueueDebug > 1 { + log.Printf("'%s' received AMQP REQUEST (of not-yet-determined type)", com.Conf.Live.Nodename) + } + + // Decode JSON request + var req com.LiveDBRequest + err = json.Unmarshal(msg.Body, &req) + if err != nil { + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} + err = com.MQResponse("NOT-YET-DETERMINED", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' the main live node switch{} while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 1 { + log.Printf("Decoded request on '%s'. Correlation ID: '%s', request operation: '%s', request query: '%v'", com.Conf.Live.Nodename, msg.CorrelationId, req.Operation, req.Data) + } else if com.JobQueueDebug == 1 { + log.Printf("Decoded request on '%s'. Correlation ID: '%s', request operation: '%s'", com.Conf.Live.Nodename, msg.CorrelationId, req.Operation) + } + + // Handle each operation + switch req.Operation { + case "backup": + err = com.SQLiteBackupLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) + if err != nil { + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} + err = com.MQResponse("BACKUP", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [BACKUP] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return a success message to the caller + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: ""} // Use an empty error message to indicate success + err = com.MQResponse("BACKUP", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP backup response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "columns": + columns, pk, err, errCode := com.SQLiteGetColumnsLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, fmt.Sprintf("%s", req.Data)) + if err != nil { + resp := com.LiveDBColumnsResponse{Node: com.Conf.Live.Nodename, Columns: []sqlite.Column{}, PkColumns: nil, Error: err.Error(), ErrCode: errCode} + err = com.MQResponse("COLUMNS", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [COLUMNS] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) + } + + // Return the columns list to the caller + resp := com.LiveDBColumnsResponse{Node: com.Conf.Live.Nodename, Columns: columns, PkColumns: pk, Error: "", ErrCode: com.JobQueueNoError} + err = com.MQResponse("COLUMNS", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP columns list response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "delete": + // Delete the database file on the node + err = com.RemoveLiveDB(req.DBOwner, req.DBName) + if err != nil { + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} + err = com.MQResponse("DELETE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [DELETE] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return a success message (empty string in this case) to the caller + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: ""} + err = com.MQResponse("DELETE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP delete database response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "execute": + // Execute a SQL statement on the database file + var rowsChanged int + rowsChanged, err = com.SQLiteExecuteQueryLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) + if err != nil { + resp := com.LiveDBExecuteResponse{Node: com.Conf.Live.Nodename, RowsChanged: 0, Error: err.Error()} + err = com.MQResponse("EXECUTE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [EXECUTE] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) + } + + // Return a success message to the caller + resp := com.LiveDBExecuteResponse{Node: com.Conf.Live.Nodename, RowsChanged: rowsChanged, Error: ""} + err = com.MQResponse("EXECUTE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP execute query response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "indexes": + var indexes []com.APIJSONIndex + indexes, err = com.SQLiteGetIndexesLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) + if err != nil { + resp := com.LiveDBIndexesResponse{Node: com.Conf.Live.Nodename, Indexes: []com.APIJSONIndex{}, Error: err.Error()} + err = com.MQResponse("INDEXES", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [INDEXES] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return the indexes list to the caller + resp := com.LiveDBIndexesResponse{Node: com.Conf.Live.Nodename, Indexes: indexes, Error: ""} + err = com.MQResponse("INDEXES", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP indexes list response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "query": + var rows com.SQLiteRecordSet + rows, err = com.SQLiteRunQueryLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, req.RequestingUser, fmt.Sprintf("%s", req.Data)) + if err != nil { + resp := com.LiveDBQueryResponse{Node: com.Conf.Live.Nodename, Results: com.SQLiteRecordSet{}, Error: err.Error()} + err = com.MQResponse("QUERY", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [QUERY] on '%s/%s': '%s'", req.DBOwner, req.DBName, req.Data) + } + + // Return the query response to the caller + resp := com.LiveDBQueryResponse{Node: com.Conf.Live.Nodename, Results: rows, Error: ""} + err = com.MQResponse("QUERY", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP query response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "rowdata": + // Extract the request information + // FIXME: Add type checks for safety instead of blind coercing + reqData := req.Data.(map[string]interface{}) + dbTable := reqData["db_table"].(string) + sortCol := reqData["sort_col"].(string) + sortDir := reqData["sort_dir"].(string) + commitID := reqData["commit_id"].(string) + maxRows := int(reqData["max_rows"].(float64)) + rowOffset := int(reqData["row_offset"].(float64)) + + // Open the SQLite database and read the row data + resp := com.LiveDBRowsResponse{Node: com.Conf.Live.Nodename, RowData: com.SQLiteRecordSet{}} + resp.Tables, resp.DefaultTable, resp.RowData, resp.DatabaseSize, err = + com.SQLiteReadDatabasePage("", "", req.RequestingUser, req.DBOwner, req.DBName, dbTable, sortCol, sortDir, commitID, rowOffset, maxRows, true) + if err != nil { + resp := com.LiveDBErrorResponse{Node: com.Conf.Live.Nodename, Error: err.Error()} + err = com.MQResponse("ROWDATA", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [ROWDATA] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return the row data to the caller + err = com.MQResponse("ROWDATA", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP query response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "size": + dbPath := filepath.Join(com.Conf.Live.StorageDir, req.DBOwner, req.DBName, "live.sqlite") + var db os.FileInfo + db, err = os.Stat(dbPath) + if err != nil { + resp := com.LiveDBSizeResponse{Node: com.Conf.Live.Nodename, Size: 0, Error: err.Error()} + err = com.MQResponse("SIZE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [SIZE] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return the database size to the caller + resp := com.LiveDBSizeResponse{Node: com.Conf.Live.Nodename, Size: db.Size(), Error: ""} + err = com.MQResponse("SIZE", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP size response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "tables": + var tables []string + tables, err = com.SQLiteGetTablesLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) + if err != nil { + resp := com.LiveDBTablesResponse{Node: com.Conf.Live.Nodename, Tables: nil, Error: err.Error()} + err = com.MQResponse("TABLES", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [TABLES] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return the tables list to the caller + resp := com.LiveDBTablesResponse{Node: com.Conf.Live.Nodename, Tables: tables, Error: ""} + err = com.MQResponse("TABLES", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP tables list response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + case "views": + var views []string + views, err = com.SQLiteGetViewsLive(com.Conf.Live.StorageDir, req.DBOwner, req.DBName) + if err != nil { + resp := com.LiveDBViewsResponse{Node: com.Conf.Live.Nodename, Views: nil, Error: err.Error()} + err = com.MQResponse("VIEWS", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing an AMQP error message response: '%s'", com.Conf.Live.Nodename, err) + } + continue + } + + if com.JobQueueDebug > 0 { + log.Printf("Running [VIEWS] on '%s/%s'", req.DBOwner, req.DBName) + } + + // Return the views list to the caller + resp := com.LiveDBViewsResponse{Node: com.Conf.Live.Nodename, Views: views, Error: ""} + err = com.MQResponse("VIEWS", msg, ch, com.Conf.Live.Nodename, resp) + if err != nil { + log.Printf("Error: occurred on '%s' in MQResponse() while constructing the AMQP views list response: '%s'", com.Conf.Live.Nodename, err) + } + continue + + default: + log.Printf("'%s' received unknown '%s' request on this queue for %s/%s", com.Conf.Live.Nodename, req.Operation, req.DBOwner, req.DBName) + } + } + }() + } + + log.Printf("Live server '%s' listening for requests", com.Conf.Live.Nodename) + // Launch go workers to process submitted jobs - go com.JobQueueCheck() - go com.JobQueueListen() + if !com.UseAMQP { + go com.JobQueueCheck() + go com.JobQueueListen() + } // Launch goroutine event generator for checking submitted jobs // TODO: This seems to work fine, but is kind of a pita to have enabled while developing this code atm. So we disable it for now. @@ -84,4 +443,9 @@ func main() { // Endless loop var forever chan struct{} <-forever + + // Close the channel to the MQ server + if com.UseAMQP { + _ = com.CloseMQChannel(ch) + } } diff --git a/package.json b/package.json index 358fe3149..c728f3dc3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,12 @@ "docker:start": "docker run -itd --rm --name dbhub-build -p 9443-9445:9443-9445/tcp -p 5550:5550/tcp dbhub-build:latest", "docker:startlocal": "docker run -itd --rm --name dbhub-build --net host --mount type=bind,src=\"$(pwd)\",target=/dbhub.io dbhub-build:latest", "docker:stop": "docker container stop dbhub-build", - "docker:tail": "docker exec -it dbhub-build tail -F /home/dbhub/output.log" + "docker:tail": "docker exec -it dbhub-build tail -F /home/dbhub/output.log", + "mq:bind": "docker exec -it dbhub-build rabbitmqctl list_bindings", + "mq:conn": "docker exec -it dbhub-build rabbitmqctl list_connections", + "mq:cons": "docker exec -it dbhub-build rabbitmqctl list_consumers", + "mq:ex": "docker exec -it dbhub-build rabbitmqctl list_exchanges", + "mq:q": "docker exec -it dbhub-build rabbitmqctl list_queues" }, "engines": { "node": "^20.10.0", diff --git a/standalone/analysis/main.go b/standalone/analysis/main.go index 44139f3c0..41d724583 100644 --- a/standalone/analysis/main.go +++ b/standalone/analysis/main.go @@ -41,7 +41,7 @@ func main() { // Connect to job queue server com.Conf.Live.Nodename = "Usage Analysis" - err = com.ConnectQueue() + com.AmqpChan, err = com.ConnectQueue() if err != nil { log.Fatal(err) } diff --git a/webui/main.go b/webui/main.go index d5897d130..c8214723f 100644 --- a/webui/main.go +++ b/webui/main.go @@ -3164,7 +3164,7 @@ func main() { } // Connect to job queue server - err = com.ConnectQueue() + com.AmqpChan, err = com.ConnectQueue() if err != nil { log.Fatal(err) } @@ -3188,11 +3188,13 @@ func main() { go com.SendEmails() // Start background goroutines to handle job queue responses - com.ResponseWaiters = com.NewResponseReceiver() - com.CheckResponsesQueue = make(chan struct{}) - com.SubmitterInstance = com.RandomString(3) - go com.ResponseQueueCheck() - go com.ResponseQueueListen() + if !com.UseAMQP { + com.ResponseWaiters = com.NewResponseReceiver() + com.CheckResponsesQueue = make(chan struct{}) + com.SubmitterInstance = com.RandomString(3) + go com.ResponseQueueCheck() + go com.ResponseQueueListen() + } // Our pages http.Handle("/", gz.GzipHandler(logReq(mainHandler))) @@ -3503,6 +3505,11 @@ func main() { if err != nil { log.Println(err) } + + err = com.CloseMQChannel(com.AmqpChan) + if err != nil { + log.Fatal(err) + } } func mainHandler(w http.ResponseWriter, r *http.Request) { @@ -5677,7 +5684,7 @@ func uploadDataHandler(w http.ResponseWriter, r *http.Request) { com.SanitiseLogString(dbOwner), com.SanitiseLogString(dbName), numBytes) // Send a request to the job queue to set up the database - liveNode, err := com.LiveCreateDB(dbOwner, dbName, objectID) + liveNode, err := com.LiveCreateDB(com.AmqpChan, dbOwner, dbName, objectID) if err != nil { log.Println(err) errorPage(w, r, http.StatusInternalServerError, err.Error())