From 573989d37e9b99bfb911dfb6cfe4080caff403d4 Mon Sep 17 00:00:00 2001 From: Myles Bock Date: Thu, 10 Sep 2020 18:25:00 -0600 Subject: [PATCH] updated createDB in app/db/admin.go to use pgx+dburl updated dropDB in app/db/admin.go to use pgx+dburl created connectAndExecute to deduplicate in app/db/admin.go replaced all use of psql binary (and dropuser) wrapped up most querying into a single function (connectAndExecute) still need to write tests and handle vendoring added github.com/xo/dburl to vendor.json added xo/dburl files to vendor cleaned up a little, added return statement added dburl/xo MIT license entry to LICENSE gave some credit where it was due reintroduced hostport, removed role attributes portion of query --- LICENSE | 4 + traffic_ops/app/db/admin.go | 129 ++--- .../vendor/github.com/xo/dburl/LICENSE | 21 + .../vendor/github.com/xo/dburl/README.md | 246 +++++++++ .../vendor/github.com/xo/dburl/dburl.go | 218 ++++++++ .../vendor/github.com/xo/dburl/dburl_test.go | 188 +++++++ traffic_ops/vendor/github.com/xo/dburl/dsn.go | 513 ++++++++++++++++++ traffic_ops/vendor/github.com/xo/dburl/go.mod | 3 + .../vendor/github.com/xo/dburl/scheme.go | 237 ++++++++ traffic_ops/vendor/github.com/xo/dburl/url.go | 202 +++++++ .../vendor/github.com/xo/dburl/util.go | 133 +++++ traffic_ops/vendor/vendor.json | 8 +- 12 files changed, 1830 insertions(+), 72 deletions(-) create mode 100644 traffic_ops/vendor/github.com/xo/dburl/LICENSE create mode 100644 traffic_ops/vendor/github.com/xo/dburl/README.md create mode 100644 traffic_ops/vendor/github.com/xo/dburl/dburl.go create mode 100644 traffic_ops/vendor/github.com/xo/dburl/dburl_test.go create mode 100644 traffic_ops/vendor/github.com/xo/dburl/dsn.go create mode 100644 traffic_ops/vendor/github.com/xo/dburl/go.mod create mode 100644 traffic_ops/vendor/github.com/xo/dburl/scheme.go create mode 100644 traffic_ops/vendor/github.com/xo/dburl/url.go create mode 100644 traffic_ops/vendor/github.com/xo/dburl/util.go diff --git a/LICENSE b/LICENSE index 63d8da31f2..c24be10e25 100644 --- a/LICENSE +++ b/LICENSE @@ -381,6 +381,10 @@ The riak-go-client component is used under the Apache license: @vendor/github.com/basho/riak-go-client/* ./vendor/github.com/basho/riak-go-client/LICENSE +The dburl component is used under the MIT license: +@traffic_ops/vendor/github.com/xo/dburl/* +./traffic_ops/vendor/github.com/xo/dburl/LICENSE + The envconfig component is used under the MIT license: @traffic_ops/vendor/github.com/kelseyhightower/envconfig/* ./traffic_ops/vendor/github.com/kelseyhightower/envconfig/LICENSE diff --git a/traffic_ops/app/db/admin.go b/traffic_ops/app/db/admin.go index c9b4507810..2eb11a2f95 100644 --- a/traffic_ops/app/db/admin.go +++ b/traffic_ops/app/db/admin.go @@ -20,7 +20,7 @@ package main */ import ( - "bytes" + "database/sql" "errors" "flag" "fmt" @@ -29,6 +29,8 @@ import ( "os/exec" "strings" + _ "github.com/lib/pq" + "github.com/xo/dburl" "gopkg.in/yaml.v2" ) @@ -171,68 +173,73 @@ func parseDBConfig() error { return nil } -func createDB() { - dbExistsCmd := exec.Command("psql", "-h", HostIP, "-U", DBSuperUser, "-p", HostPort, "-tAc", "SELECT 1 FROM pg_database WHERE datname='"+DBName+"'") - out, err := dbExistsCmd.Output() - if err != nil { - die("unable to check if DB already exists: " + err.Error()) - } - if len(out) > 0 { - fmt.Println("Database " + DBName + " already exists") - return - } - createDBCmd := exec.Command("createdb", "-h", HostIP, "-p", HostPort, "-U", DBSuperUser, "-e", "--owner", DBUser, DBName) - out, err = createDBCmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't create db " + DBName) +func connectAndExecute(query string, queryErrPrefix string, returnRows bool) *sql.Rows { + //todo prepared statements + //todo inspect results of executing the above query for all possible cases (db exists, db dne, db inaccessible) + //todo determine test coverage of this portion + if db, err := dburl.Open(fmt.Sprintf("pg://%s:%s@%s:%s/", DBSuperUser, DBPassword, HostIP, HostPort)); err == nil { + if !returnRows { + if _, err := db.Exec(query); err != nil { + die(queryErrPrefix + ":" + err.Error()) + } + } else { + if rows, err := db.Query(query); err != nil { + die(queryErrPrefix + ":" + err.Error()) + } else { + return rows + } + } + } else { + die("couldn't connect to pg at " + HostIP + ": " + err.Error()) } + return nil +} + +func createDB() { + fmt.Println("Creating database: " + DBName) + connectAndExecute(`CREATE DATABASE `+DBName+` OWNER `+DBUser, "Couldn't create database "+DBName, false) } func dropDB() { fmt.Println("Dropping database: " + DBName) - cmd := exec.Command("dropdb", "-h", HostIP, "-p", HostPort, "-U", DBSuperUser, "-e", "--if-exists", DBName) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't drop db " + DBName) - } + connectAndExecute(`DROP DATABASE `+DBName, "Couldn't drop database:"+DBName, false) } func createUser() { fmt.Println("Creating user: " + DBUser) - userExistsCmd := exec.Command("psql", "-h", HostIP, "-U", DBSuperUser, "-p", HostPort, "-tAc", "SELECT 1 FROM pg_roles WHERE rolname='"+DBUser+"'") - out, err := userExistsCmd.Output() - if err != nil { - die("unable to check if user already exists: " + err.Error()) - } - if len(out) > 0 { - fmt.Println("User " + DBUser + " already exists") - return - } - createUserCmd := exec.Command("psql", "-h", HostIP, "-p", HostPort, "-U", DBSuperUser, "-etAc", "CREATE USER "+DBUser+" WITH LOGIN ENCRYPTED PASSWORD '"+DBPassword+"'") - out, err = createUserCmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't create user " + DBUser) + + userExistsCmd := connectAndExecute( + "SELECT 1 FROM pg_roles WHERE rolname='"+DBUser+"'", + "Unable to query for user existence "+DBUser, true) + var exists int + if userExistsCmd.Scan(&exists); exists != 1 { + fmt.Println("User does not exist, creating: " + DBUser) + connectAndExecute( + "CREATE USER "+DBUser+" WITH LOGIN ENCRYPTED PASSWORD '"+DBPassword+"'", + "Unable to create user", + false) } } func dropUser() { - cmd := exec.Command("dropuser", "-h", HostIP, "-p", HostPort, "-U", DBSuperUser, "-i", "-e", DBUser) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't drop user " + DBUser) - } + // TODO do i need to handle owned schema before dropping? + connectAndExecute(`DROP ROLE `+DBUser, `Couldn't drop user `+DBUser, false) } func showUsers() { - cmd := exec.Command("psql", "-h", HostIP, "-p", HostPort, "-U", DBSuperUser, "-ec", `\du`) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't show users") + rows := connectAndExecute(` + SELECT usename AS role_name + FROM pg_catalog.pg_user + ORDER BY role_name desc;`, `Couldn't show users`, true) + // todo cleaner output + fmt.Println("role_name | role_attributes") + var roleName, roleAttributes string + for rows.Next() { + if err := rows.Scan(&roleName, &roleAttributes); err == nil { + fmt.Printf("%s | %s", roleName, roleAttributes) + } else { + fmt.Printf("Couldn't read row: %s", err.Error()) + } } } @@ -276,14 +283,7 @@ func seed() { if err != nil { die("unable to read '" + DBSeedsPath + "': " + err.Error()) } - cmd := exec.Command("psql", "-h", HostIP, "-p", HostPort, "-d", DBName, "-U", DBUser, "-e", "-v", "ON_ERROR_STOP=1") - cmd.Stdin = bytes.NewBuffer(seedsBytes) - cmd.Env = append(os.Environ(), "PGPASSWORD="+DBPassword) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't patch database w/ required data") - } + connectAndExecute(string(seedsBytes), "Couldn't seed database", false) } func loadSchema() { @@ -292,16 +292,10 @@ func loadSchema() { if err != nil { die("unable to read '" + DBSchemaPath + "': " + err.Error()) } - cmd := exec.Command("psql", "-h", HostIP, "-p", HostPort, "-d", DBName, "-U", DBUser, "-e", "-v", "ON_ERROR_STOP=1") - cmd.Stdin = bytes.NewBuffer(schemaBytes) - cmd.Env = append(os.Environ(), "PGPASSWORD="+DBPassword) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't create database tables") - } + connectAndExecute(string(schemaBytes), "Couldn't create tables", false) } +//TODO remove this after perlectomy func reverseSchema() { fmt.Fprintf(os.Stderr, "WARNING: the '%s' command will be removed with Traffic Ops Perl because it will no longer be necessary\n", CmdReverseSchema) cmd := exec.Command("db/reverse_schema.pl") @@ -319,14 +313,7 @@ func patch() { if err != nil { die("unable to read '" + DBPatchesPath + "': " + err.Error()) } - cmd := exec.Command("psql", "-h", HostIP, "-p", HostPort, "-d", DBName, "-U", DBUser, "-e", "-v", "ON_ERROR_STOP=1") - cmd.Stdin = bytes.NewBuffer(patchesBytes) - cmd.Env = append(os.Environ(), "PGPASSWORD="+DBPassword) - out, err := cmd.CombinedOutput() - fmt.Printf("%s", out) - if err != nil { - die("Can't patch database w/ required data") - } + connectAndExecute(string(patchesBytes), "Couldn't patch database", false) } func goose(arg string) { diff --git a/traffic_ops/vendor/github.com/xo/dburl/LICENSE b/traffic_ops/vendor/github.com/xo/dburl/LICENSE new file mode 100644 index 0000000000..e3b2f88e33 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2020 Kenneth Shaw + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/traffic_ops/vendor/github.com/xo/dburl/README.md b/traffic_ops/vendor/github.com/xo/dburl/README.md new file mode 100644 index 0000000000..e9b5718ab9 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/README.md @@ -0,0 +1,246 @@ +# dburl [![GoDoc][godoc]][godoc-link] + +Package `dburl` provides a standard, URL style mechanism for parsing and +opening SQL database connection strings for [Go][go-project]. Provides +standardized way to [parse][godoc-parse] and [open][godoc-open] URLs for +popular databases PostgreSQL, MySQL, SQLite3, Oracle Database, Microsoft SQL +Server, in addition to most other SQL databases with a publicly available Go +driver. + +[godoc]: https://godoc.org/github.com/xo/dburl?status.svg (GoDoc) +[godoc-link]: https://godoc.org/github.com/xo/dburl + +[Overview][] | [Quickstart][] | [Examples][] | [Schemes][] | [Installing][] | [Using][] | [About][] + +[Overview]: #database-connection-url-overview (Database Connection URL Overview) +[Quickstart]: #quickstart (Quickstart) +[Examples]: #example-urls (Example URLs) +[Schemes]: #protocol-schemes-and-aliases (Protocol Schemes and Aliases) +[Installing]: #installing (Installing) +[Using]: #using (Using) +[About]: #about (About) + +## Database Connection URL Overview + +Supported database connection URLs are of the form: + +``` + protocol+transport://user:pass@host/dbname?opt1=a&opt2=b + protocol:/path/to/file +``` + +Where: + +| Component | Description | +|--------------------|--------------------------------------------------------------------------------------| +| protocol | driver name or alias (see below) | +| transport | "tcp", "udp", "unix" or driver name (odbc/oleodbc) | +| user | username | +| pass | password | +| host | host | +| dbname* | database, instance, or service name/ID to connect to | +| ?opt1=... | additional database driver options (see respective SQL driver for available options) | + +* for Microsoft SQL Server, `/dbname` can be +`/instance/dbname`, where `/instance` is optional. For Oracle Database, +`/dbname` is of the form `/service/dbname` where `/service` is the service name +or SID, and `/dbname` is optional. Please see below for examples. + +## Quickstart + +Database connection URLs in the above format can be parsed with the +[`dburl.Parse` func][godoc-parse] as such: + +```go +import ( + "github.com/xo/dburl" +) +u, err := dburl.Parse("postgresql://user:pass@localhost/mydatabase/?sslmode=disable") +if err != nil { /* ... */ } +``` + +Additionally, a simple helper, [`dburl.Open`][godoc-open], is provided that +will parse, open, and return a [standard `sql.DB` database][godoc-sql-db] +connection: + +```go +import ( + "github.com/xo/dburl" +) +db, err := dburl.Open("sqlite:mydatabase.sqlite3?loc=auto") +if err != nil { /* ... */ } +``` + +## Example URLs + +The following are example database connection URLs that can be handled by +[`dburl.Parse`][godoc-parse] and [`dburl.Open`][godoc-open]: + +``` + postgres://user:pass@localhost/dbname + pg://user:pass@localhost/dbname?sslmode=disable + mysql://user:pass@localhost/dbname + mysql:/var/run/mysqld/mysqld.sock + sqlserver://user:pass@remote-host.com/dbname + mssql://user:pass@remote-host.com/instance/dbname + ms://user:pass@remote-host.com:port/instance/dbname?keepAlive=10 + oracle://user:pass@somehost.com/sid + sap://user:pass@localhost/dbname + sqlite:/path/to/file.db + file:myfile.sqlite3?loc=auto + odbc+postgres://user:pass@localhost:port/dbname?option1= +``` + +## Protocol Schemes and Aliases + +The following protocols schemes (ie, driver) and their associated aliases are +supported out of the box: + +| Database (scheme/driver) | Protocol Aliases [real driver] | +|--------------------------------|------------------------------------------| +| Microsoft SQL Server (mssql) | ms, sqlserver | +| MySQL (mysql) | my, mariadb, maria, percona, aurora | +| Oracle Database (godror) | or, ora, oracle, oci, oci8, odpi, odpi-c | +| PostgreSQL (postgres) | pg, postgresql, pgsql | +| SQLite3 (sqlite3) | sq, sqlite, file | +| | | +| Amazon Redshift (redshift) | rs [postgres] | +| CockroachDB (cockroachdb) | cr, cockroach, crdb, cdb [postgres] | +| MemSQL (memsql) | me [mysql] | +| TiDB (tidb) | ti [mysql] | +| Vitess (vitess) | vt [mysql] | +| | | +| MySQL (mymysql) | zm, mymy | +| PostgreSQL (pgx) | px | +| | | +| Apache Avatica (avatica) | av, phoenix | +| Apache Ignite (ignite) | ig, gridgain | +| Cassandra (cql) | ca, cassandra, datastax, scy, scylla | +| ClickHouse (clickhouse) | ch | +| Couchbase (n1ql) | n1, couchbase | +| Cznic QL (ql) | ql, cznic, cznicql | +| Firebird SQL (firebirdsql) | fb, firebird | +| Microsoft ADODB (adodb) | ad, ado | +| ModernC SQLite (moderncsqlite) | mq, modernsqlite | +| ODBC (odbc) | od | +| OLE ODBC (oleodbc) | oo, ole, oleodbc [adodb] | +| Presto (presto) | pr, prestodb, prestos, prs, prestodbs | +| SAP ASE (tds) | ax, ase, sapase | +| SAP HANA (hdb) | sa, saphana, sap, hana | +| Snowflake (snowflake) | sf | +| Vertica (vertica) | ve | +| VoltDB (voltdb) | vo, volt, vdb | + +Any protocol scheme `alias://` can be used in place of `protocol://`, and will +work identically with [`dburl.Parse`][godoc-parse] and [`dburl.Open`][godoc-open]. + +## Installing + +Install in the usual Go fashion: + +```sh +go get -u github.com/xo/dburl +``` + +## Using + +Please note that `dburl` does not import actual SQL drivers, and only provides +a standard way to [parse][godoc-parse]/[open][godoc-open] respective database +connection URLs. + +For reference, these are the following "expected" SQL drivers that would need +to be imported: + +| Database (driver) | Package | +|--------------------------------|---------------------------------------------------------------------------------------------| +| Microsoft SQL Server (mssql) | [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) | +| MySQL (mysql) | [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) | +| Oracle Database (godror) | [github.com/godror/godror](github.com/godror/godror) | +| PostgreSQL (postgres) | [github.com/lib/pq](https://github.com/lib/pq) | +| SQLite3 (sqlite3) | [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) | +| | | +| Amazon Redshift (redshift) | [github.com/lib/pq](https://github.com/lib/pq) | +| CockroachDB (cockroachdb) | [github.com/lib/pq](https://github.com/lib/pq) | +| MemSQL (memsql) | [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) | +| TiDB (tidb) | [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) | +| Vitess (vitess) | [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) | +| | | +| MySQL (mymysql) | [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) | +| PostgreSQL (pgx) | [github.com/jackc/pgx/stdlib](https://github.com/jackc/pgx) | +| | | +| Apache Avatica (avatica) | [github.com/Boostport/avatica](https://github.com/Boostport/avatica) | +| Apache Ignite (ignite) | [github.com/amsokol/ignite-go-client/sql](https://github.com/amsokol/ignite-go-client) | +| Cassandra (cql) | [github.com/MichaelS11/go-cql-driver](https://github.com/MichaelS11/go-cql-driver) | +| ClickHouse (clickhouse) | [github.com/ClickHouse/clickhouse-go](https://github.com/ClickHouse/clickhouse-go) | +| Couchbase (n1ql) | [github.com/couchbase/go_n1ql](https://github.com/couchbase/go_n1ql) | +| Cznic QL (ql) | [modernc.org/ql](https://modernc.org/ql) | +| Firebird SQL (firebirdsql) | [github.com/nakagami/firebirdsql](https://github.com/nakagami/firebirdsql) | +| Microsoft ADODB (adodb) | [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) | +| ModernC SQLite (moderncsqlite) | [modernc.org/sqlite](https://modernc.org/sqlite) | +| ODBC (odbc) | [github.com/alexbrainman/odbc](https://github.com/alexbrainman/odbc) | +| OLE ODBC (oleodbc) | [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) | +| Presto (presto) | [github.com/prestodb/presto-go-client/presto](https://github.com/prestodb/presto-go-client) | +| SAP ASE (tds) | [github.com/thda/tds](https://github.com/thda/tds) | +| SAP HANA (hdb) | [github.com/SAP/go-hdb/driver](https://github.com/SAP/go-hdb) | +| Snowflake (snowflake) | [github.com/snowflakedb/gosnowflake](https://github.com/snowflakedb/gosnowflake) | +| Vertica (vertica) | [github.com/vertica/vertica-sql-go](https://github.com/vertica/vertica-sql-go) | +| VoltDB (voltdb) | [github.com/VoltDB/voltdb-client-go/voltdbclient](github.com/VoltDB/voltdb-client-go]) | + +Please see [the `dburl` GoDoc listing][godoc-link] for the full API +documentation. + +### URL Parsing Rules + +[`dburl.Parse`][godoc-parse] and [`dburl.Open`][godoc-open] rely primarily on +Go's standard [`net/url.URL`][godoc-net-url] type, and as such, parsing or +opening database connection URLs with `dburl` are subject to the same rules, +conventions, and semantics as [Go's `net/url.Parse` func][godoc-net-url-parse]. + +## Example + +A [full example](_example/example.go) for reference: + +```go +// _example/example.go +package main + +import ( + "fmt" + "log" + + _ "github.com/denisenkom/go-mssqldb" + "github.com/xo/dburl" +) + +func main() { + db, err := dburl.Open("sqlserver://user:pass@localhost/dbname") + if err != nil { + log.Fatal(err) + } + + var name string + err = db.QueryRow(`SELECT name FROM mytable WHERE id=10`).Scan(&name) + if err != nil { + log.Fatal(err) + } + + fmt.Printf(">> got: %s\n", name) +} +``` + +## About + +`dburl` was built primarily to support these projects: + +* [usql][usql] - a universal command-line interface for SQL databases +* [xo][xo] - a command-line tool to generate Go code from a database schema + +[go-project]: https://golang.org/project +[godoc-open]: https://godoc.org/github.com/xo/dburl#Open +[godoc-parse]: https://godoc.org/github.com/xo/dburl#Parse +[godoc-sql-db]: https://godoc.org/database/sql#DB +[godoc-net-url]: https://godoc.org/net/url#URL +[godoc-net-url-parse]: https://godoc.org/net/url#URL.Parse + +[usql]: https://github.com/xo/usql +[xo]: https://github.com/xo/xo diff --git a/traffic_ops/vendor/github.com/xo/dburl/dburl.go b/traffic_ops/vendor/github.com/xo/dburl/dburl.go new file mode 100644 index 0000000000..52251f8f9d --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/dburl.go @@ -0,0 +1,218 @@ +// Package dburl provides a standard, URL style mechanism for parsing and +// opening SQL database connection strings for Go. Provides standardized way to +// parse and open URLs for popular databases PostgreSQL, MySQL, SQLite3, Oracle +// Database, Microsoft SQL Server, in addition to most other SQL databases with +// a publicly available Go driver. +// +// Database Connection URL Overview +// +// Supported database connection URLs are of the form: +// +// protocol+transport://user:pass@host/dbname?opt1=a&opt2=b +// protocol:/path/to/file +// +// Where: +// +// protocol - driver name or alias (see below) +// transport - "tcp", "udp", "unix" or driver name (odbc/oleodbc) | +// user - username +// pass - password +// host - host +// dbname* - database, instance, or service name/id to connect to +// ?opt1=... - additional database driver options +// (see respective SQL driver for available options) +// +// * for Microsoft SQL Server, /dbname can be /instance/dbname, where /instance +// is optional. For Oracle Database, /dbname is of the form /service/dbname +// where /service is the service name or SID, and /dbname is optional. Please +// see below for examples. +// +// Quickstart +// +// Database connection URLs in the above format can be parsed with Parse as such: +// +// import ( +// "github.com/xo/dburl" +// ) +// u, err := dburl.Parse("postgresql://user:pass@localhost/mydatabase/?sslmode=disable") +// if err != nil { /* ... */ } +// +// Additionally, a simple helper, Open, is provided that will parse, open, and +// return a standard sql.DB database connection: +// +// import ( +// "github.com/xo/dburl" +// ) +// db, err := dburl.Open("sqlite:mydatabase.sqlite3?loc=auto") +// if err != nil { /* ... */ } +// +// Example URLs +// +// The following are example database connection URLs that can be handled by +// Parse and Open: +// +// postgres://user:pass@localhost/dbname +// pg://user:pass@localhost/dbname?sslmode=disable +// mysql://user:pass@localhost/dbname +// mysql:/var/run/mysqld/mysqld.sock +// sqlserver://user:pass@remote-host.com/dbname +// mssql://user:pass@remote-host.com/instance/dbname +// ms://user:pass@remote-host.com:port/instance/dbname?keepAlive=10 +// oracle://user:pass@somehost.com/sid +// sap://user:pass@localhost/dbname +// sqlite:/path/to/file.db +// file:myfile.sqlite3?loc=auto +// odbc+postgres://user:pass@localhost:port/dbname?option1= +// +// Protocol Schemes and Aliases +// +// The following protocols schemes (ie, driver) and their associated aliases +// are supported out of the box: +// +// Database (scheme/driver) | Protocol Aliases [real driver] +// -------------------------------|-------------------------------------------- +// Microsoft SQL Server (mssql) | ms, sqlserver +// MySQL (mysql) | my, mariadb, maria, percona, aurora +// Oracle Database (godror) | or, ora, oci, oci8, odpi, odpi-c +// PostgreSQL (postgres) | pg, postgresql, pgsql +// SQLite3 (sqlite3) | sq, sqlite, file +// -------------------------------|-------------------------------------------- +// Amazon Redshift (redshift) | rs [postgres] +// CockroachDB (cockroachdb) | cr, cockroach, crdb, cdb [postgres] +// MemSQL (memsql) | me [mysql] +// TiDB (tidb) | ti [mysql] +// Vitess (vitess) | vt [mysql] +// -------------------------------|-------------------------------------------- +// MySQL (mymysql) | zm, mymy +// PostgreSQL (pgx) | px +// -------------------------------|-------------------------------------------- +// Apache Avatica (avatica) | av, phoenix +// Apache Ignite (ignite) | ig, gridgain +// Cassandra (cql) | ca, cassandra, datastax, scy, scylla +// ClickHouse (clickhouse) | ch +// Couchbase (n1ql) | n1, couchbase +// Cznic QL (ql) | ql, cznic, cznicql +// Firebird SQL (firebirdsql) | fb, firebird +// Microsoft ADODB (adodb) | ad, ado +// ModernC SQLite (moderncsqlite) | mq, modernsqlite +// ODBC (odbc) | od +// OLE ODBC (oleodbc) | oo, ole, oleodbc [adodb] +// Presto (presto) | pr, prestodb, prestos, prs, prestodbs +// SAP ASE (tds) | ax, ase, sapase +// SAP HANA (hdb) | sa, saphana, sap, hana +// Snowflake (snowflake) | sf +// Vertica (vertica) | ve +// VoltDB (voltdb) | vo, volt, vdb +// +// Any protocol scheme alias:// can be used in place of protocol://, and will +// work identically with Parse and Open. +// +// Using +// +// Please note that the dburl package does not import actual SQL drivers, and +// only provides a standard way to parse/open respective database connection URLs. +// +// For reference, these are the following "expected" SQL drivers that would need +// to be imported: +// +// Database (scheme/driver) | Package +// -------------------------------|------------------------------------------------- +// Microsoft SQL Server (mssql) | github.com/denisenkom/go-mssqldb +// MySQL (mysql) | github.com/go-sql-driver/mysql +// Oracle Database (godror) | github.com/godror/godror +// PostgreSQL (postgres) | github.com/lib/pq +// SQLite3 (sqlite3) | github.com/mattn/go-sqlite3 +// -------------------------------|------------------------------------------------- +// Amazon Redshift (redshift) | github.com/lib/pq +// CockroachDB (cockroachdb) | github.com/lib/pq +// MemSQL (memsql) | github.com/go-sql-driver/mysql +// TiDB (tidb) | github.com/go-sql-driver/mysql +// Vitess (vitess) | github.com/go-sql-driver/mysql +// -------------------------------|------------------------------------------------- +// MySQL (mymysql) | github.com/ziutek/mymysql/godrv +// PostgreSQL (pgx) | github.com/jackc/pgx/stdlib +// -------------------------------|------------------------------------------------- +// Apache Avatica (avatica) | github.com/Boostport/avatica +// Apache Ignite (ignite) | github.com/amsokol/ignite-go-client/sql +// Cassandra (cql) | github.com/MichaelS11/go-cql-driver +// ClickHouse (clickhouse) | github.com/ClickHouse/clickhouse-go +// Couchbase (n1ql) | github.com/couchbase/go_n1ql +// Cznic QL (ql) | modernc.org/ql +// Firebird SQL (firebirdsql) | github.com/nakagami/firebirdsql +// Microsoft ADODB (adodb) | github.com/mattn/go-adodb +// ModernC SQLite (moderncsqlite) | modernc.org/sqlite +// ODBC (odbc) | github.com/alexbrainman/odbc +// OLE ODBC (oleodbc)* | github.com/mattn/go-adodb +// Presto (presto) | github.com/prestodb/presto-go-client +// SAP ASE (tds) | github.com/thda/tds +// SAP HANA (hdb) | github.com/SAP/go-hdb/driver +// Snowflake (snowflake) | github.com/snowflakedb/gosnowflake +// Vertica (vertica) | github.com/vertica/vertica-sql-go +// VoltDB (voltdb) | github.com/VoltDB/voltdb-client-go/voltdbclient +// +// * OLE ODBC is a special alias for using the "MSDASQL.1" OLE provider with the +// ADODB driver on Windows. oleodbc:// URLs will be converted to the equivalent +// ADODB DSN with "Extended Properties" having the respective ODBC parameters, +// including the underlying transport prootocol. As such, oleodbc+protocol://user:pass@host/dbname +// URLs are equivalent to adodb://MSDASQL.1/?Extended+Properties=.... on +// Windows. See GenOLEODBC for information regarding how URL components are +// mapped and passed to ADODB's Extended Properties parameter. +// +// URL Parsing Rules +// +// Parse and Open rely heavily on the standard net/url.URL type, as such +// parsing rules have the same conventions/semantics as any URL parsed by the +// standard library's net/url.Parse. +// +// Related Projects +// +// This package was written mainly to support xo (https://github.com/xo/xo) +// and usql (https://github.com/xo/usql). +package dburl + +import ( + "database/sql" +) + +// Error is a dburl error. +type Error string + +// Error satisfies the error interface. +func (err Error) Error() string { + return string(err) +} + +const ( + // ErrInvalidDatabaseScheme is the invalid database scheme error. + ErrInvalidDatabaseScheme Error = "invalid database scheme" + + // ErrUnknownDatabaseScheme is the unknown database type error. + ErrUnknownDatabaseScheme Error = "unknown database scheme" + + // ErrInvalidTransportProtocol is the invalid transport protocol error. + ErrInvalidTransportProtocol Error = "invalid transport protocol" + + // ErrRelativePathNotSupported is the relative paths not supported error. + ErrRelativePathNotSupported Error = "relative path not supported" + + // ErrMissingHost is the missing host error. + ErrMissingHost Error = "missing host" + + // ErrMissingPath is the missing path error. + ErrMissingPath Error = "missing path" + + // ErrMissingUser is the missing user error. + ErrMissingUser Error = "missing user" +) + +// Open takes a urlstr like "protocol+transport://user:pass@host/dbname?option1=a&option2=b" +// and creates a standard sql.DB connection. +// +// See Parse for information on formatting URLs to work properly with Open. +func Open(urlstr string) (*sql.DB, error) { + u, err := Parse(urlstr) + if err != nil { + return nil, err + } + return sql.Open(u.Driver, u.DSN) +} diff --git a/traffic_ops/vendor/github.com/xo/dburl/dburl_test.go b/traffic_ops/vendor/github.com/xo/dburl/dburl_test.go new file mode 100644 index 0000000000..4ba7dc690d --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/dburl_test.go @@ -0,0 +1,188 @@ +package dburl + +import ( + "os" + "testing" +) + +func TestBadParse(t *testing.T) { + tests := []struct { + s string + exp error + }{ + {``, ErrInvalidDatabaseScheme}, + {` `, ErrInvalidDatabaseScheme}, + {`pgsqlx://`, ErrUnknownDatabaseScheme}, + {`m`, ErrInvalidDatabaseScheme}, + {`pg+udp://user:pass@localhost/dbname`, ErrInvalidTransportProtocol}, + {`sqlite+unix://`, ErrInvalidTransportProtocol}, + {`sqlite+tcp://`, ErrInvalidTransportProtocol}, + {`file+tcp://`, ErrInvalidTransportProtocol}, + {`file://`, ErrMissingPath}, + {`ql://`, ErrMissingPath}, + {`mssql+tcp://user:pass@host/dbname`, ErrInvalidTransportProtocol}, + {`mssql+aoeu://`, ErrInvalidTransportProtocol}, + {`mssql+unix:/var/run/mssql.sock`, ErrInvalidTransportProtocol}, + {`mssql+udp:localhost:155`, ErrInvalidTransportProtocol}, + {`adodb+foo+bar://provider/database`, ErrInvalidTransportProtocol}, + {`memsql:/var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`tidb:/var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`vitess:/var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`memsql+unix:///var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`tidb+unix:///var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`vitess+unix:///var/run/mysqld/mysqld.sock`, ErrInvalidTransportProtocol}, + {`cockroach:/var/run/postgresql`, ErrInvalidTransportProtocol}, + {`cockroach+unix:/var/run/postgresql`, ErrInvalidTransportProtocol}, + {`cockroach:./path`, ErrInvalidTransportProtocol}, + {`cockroach+unix:./path`, ErrInvalidTransportProtocol}, + {`redshift:/var/run/postgresql`, ErrInvalidTransportProtocol}, + {`redshift+unix:/var/run/postgresql`, ErrInvalidTransportProtocol}, + {`redshift:./path`, ErrInvalidTransportProtocol}, + {`redshift+unix:./path`, ErrInvalidTransportProtocol}, + {`pg:./path/to/socket`, ErrRelativePathNotSupported}, // relative paths are not possible for postgres sockets + {`pg+unix:./path/to/socket`, ErrRelativePathNotSupported}, + {`snowflake://`, ErrMissingHost}, + {`sf://`, ErrMissingHost}, + {`snowflake://account`, ErrMissingUser}, + {`sf://account`, ErrMissingUser}, + {`mq+unix://`, ErrInvalidTransportProtocol}, + {`mq+tcp://`, ErrInvalidTransportProtocol}, + } + for i, test := range tests { + _, err := Parse(test.s) + if err == nil { + t.Errorf("test %d expected error parsing `%s`, got: nil", i, test.s) + continue + } + if err != test.exp { + t.Errorf("test %d expected error parsing `%s`: `%v`, got: `%v`", i, test.s, test.exp, err) + } + } +} + +func TestParse(t *testing.T) { + tests := []struct { + s string + d string + exp string + path string + }{ + {`pg:`, `postgres`, ``, ``}, + {`pg://`, `postgres`, ``, ``}, + {`pg:user:pass@localhost/booktest`, `postgres`, `dbname=booktest host=localhost password=pass user=user`, ``}, + {`pg:/var/run/postgresql`, `postgres`, `host=/var/run/postgresql`, `/var/run/postgresql`}, + {`pg:/var/run/postgresql:6666/mydb`, `postgres`, `dbname=mydb host=/var/run/postgresql port=6666`, `/var/run/postgresql`}, + {`pg:/var/run/postgresql/mydb`, `postgres`, `dbname=mydb host=/var/run/postgresql`, `/var/run/postgresql`}, + {`pg:/var/run/postgresql:7777`, `postgres`, `host=/var/run/postgresql port=7777`, `/var/run/postgresql`}, + {`pg+unix:/var/run/postgresql:4444/booktest`, `postgres`, `dbname=booktest host=/var/run/postgresql port=4444`, `/var/run/postgresql`}, + {`pg:user:pass@/var/run/postgresql/mydb`, `postgres`, `dbname=mydb host=/var/run/postgresql password=pass user=user`, `/var/run/postgresql`}, + {`pg:user:pass@/really/bad/path`, `postgres`, `host=/really/bad/path password=pass user=user`, ``}, + + {`my:`, `mysql`, `tcp(127.0.0.1:3306)/`, ``}, // 10 + {`my://`, `mysql`, `tcp(127.0.0.1:3306)/`, ``}, + {`my:booktest:booktest@localhost/booktest`, `mysql`, `booktest:booktest@tcp(localhost:3306)/booktest`, ``}, + {`my:/var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mysql`, `unix(/var/run/mysqld/mysqld.sock)/mydb?timeout=90`, `/var/run/mysqld/mysqld.sock`}, + {`my:///var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mysql`, `unix(/var/run/mysqld/mysqld.sock)/mydb?timeout=90`, `/var/run/mysqld/mysqld.sock`}, + {`my+unix:user:pass@mysqld.sock?timeout=90`, `mysql`, `user:pass@unix(mysqld.sock)/?timeout=90`, ``}, + {`my:./path/to/socket`, `mysql`, `unix(path/to/socket)/`, ``}, + {`my+unix:./path/to/socket`, `mysql`, `unix(path/to/socket)/`, ``}, + + {`mymy:`, `mymysql`, `tcp:127.0.0.1:3306*//`, ``}, // 18 + {`mymy://`, `mymysql`, `tcp:127.0.0.1:3306*//`, ``}, + {`mymy:user:pass@localhost/booktest`, `mymysql`, `tcp:localhost:3306*booktest/user/pass`, ``}, + {`mymy:/var/run/mysqld/mysqld.sock/mydb?timeout=90&test=true`, `mymysql`, `unix:/var/run/mysqld/mysqld.sock,test,timeout=90*mydb`, `/var/run/mysqld/mysqld.sock`}, + {`mymy:///var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mymysql`, `unix:/var/run/mysqld/mysqld.sock,timeout=90*mydb`, `/var/run/mysqld/mysqld.sock`}, + {`mymy+unix:user:pass@mysqld.sock?timeout=90`, `mymysql`, `unix:mysqld.sock,timeout=90*/user/pass`, ``}, + {`mymy:./path/to/socket`, `mymysql`, `unix:path/to/socket*//`, ``}, + {`mymy+unix:./path/to/socket`, `mymysql`, `unix:path/to/socket*//`, ``}, + + {`mssql://`, `mssql`, ``, ``}, // 26 + {`mssql://user:pass@localhost/dbname`, `mssql`, `Database=dbname;Password=pass;Server=localhost;User ID=user`, ``}, + {`mssql://user@localhost/service/dbname`, `mssql`, `Database=dbname;Server=localhost\service;User ID=user`, ``}, + {`mssql://user:!234%23$@localhost:1580/dbname`, `mssql`, `Database=dbname;Password=!234#$;Port=1580;Server=localhost;User ID=user`, ``}, + + { + `adodb://Microsoft.ACE.OLEDB.12.0?Extended+Properties=%22Text%3BHDR%3DNO%3BFMT%3DDelimited%22`, `adodb`, // 30 + `Data Source=.;Extended Properties="Text;HDR=NO;FMT=Delimited";Provider=Microsoft.ACE.OLEDB.12.0`, ``, + }, + { + `adodb://user:pass@Provider.Name:1542/Oracle8i/dbname`, `adodb`, + `Data Source=Oracle8i;Database=dbname;Password=pass;Port=1542;Provider=Provider.Name;User ID=user`, ``, + }, + { + `oo+Postgres+Unicode://user:pass@host:5432/dbname`, `adodb`, + `Provider=MSDASQL.1;Extended Properties="Database=dbname;Driver={Postgres Unicode};PWD=pass;Port=5432;Server=host;UID=user"`, ``, + }, + + {`file:/path/to/file.sqlite3`, `sqlite3`, `/path/to/file.sqlite3`, ``}, // 33 + {`sqlite:///path/to/file.sqlite3`, `sqlite3`, `/path/to/file.sqlite3`, ``}, + {`sq://path/to/file.sqlite3`, `sqlite3`, `path/to/file.sqlite3`, ``}, + {`sq:path/to/file.sqlite3`, `sqlite3`, `path/to/file.sqlite3`, ``}, + {`sq:./path/to/file.sqlite3`, `sqlite3`, `./path/to/file.sqlite3`, ``}, + {`sq://./path/to/file.sqlite3?loc=auto`, `sqlite3`, `./path/to/file.sqlite3?loc=auto`, ``}, + {`sq::memory:?loc=auto`, `sqlite3`, `:memory:?loc=auto`, ``}, + {`sq://:memory:?loc=auto`, `sqlite3`, `:memory:?loc=auto`, ``}, + + {`or://user:pass@localhost:3000/sidname`, `godror`, `user/pass@//localhost:3000/sidname`, ``}, // 41 + {`or://localhost`, `godror`, `localhost`, ``}, + {`oracle://user:pass@localhost`, `godror`, `user/pass@//localhost`, ``}, + {`oracle://user:pass@localhost/service_name/instance_name`, `godror`, `user/pass@//localhost/service_name/instance_name`, ``}, + {`oracle://user:pass@localhost:2000/xe.oracle.docker`, `godror`, `user/pass@//localhost:2000/xe.oracle.docker`, ``}, + {`or://username:password@host/ORCL`, `godror`, `username/password@//host/ORCL`, ``}, + {`odpi://username:password@sales-server:1521/sales.us.acme.com`, `godror`, `username/password@//sales-server:1521/sales.us.acme.com`, ``}, + {`godror://username:password@sales-server.us.acme.com/sales.us.oracle.com`, `godror`, `username/password@//sales-server.us.acme.com/sales.us.oracle.com`, ``}, + + {`presto://host:8001/`, `presto`, `http://user@host:8001?catalog=default`, ``}, // 49 + {`presto://host/catalogname/schemaname`, `presto`, `http://user@host:8080?catalog=catalogname&schema=schemaname`, ``}, + {`prs://admin@host/catalogname`, `presto`, `https://admin@host:8443?catalog=catalogname`, ``}, + {`prestodbs://admin:pass@host:9998/catalogname`, `presto`, `https://admin:pass@host:9998?catalog=catalogname`, ``}, + + {`ca://host`, `cql`, `host:9042`, ``}, // 53 + {`cassandra://host:9999`, `cql`, `host:9999`, ``}, + {`scy://user@host:9999`, `cql`, `host:9999?username=user`, ``}, + {`scylla://user@host:9999?timeout=1000`, `cql`, `host:9999?timeout=1000&username=user`, ``}, + {`datastax://user:pass@localhost:9999/?timeout=1000`, `cql`, `localhost:9999?password=pass&timeout=1000&username=user`, ``}, + {`ca://user:pass@localhost:9999/dbname?timeout=1000`, `cql`, `localhost:9999?keyspace=dbname&password=pass&timeout=1000&username=user`, ``}, + + {`ig://host`, `ignite`, `tcp://host:10800`, ``}, // 59 + {`ignite://host:9999`, `ignite`, `tcp://host:9999`, ``}, + {`gridgain://user@host:9999`, `ignite`, `tcp://host:9999?username=user`, ``}, + {`ig://user@host:9999?timeout=1000`, `ignite`, `tcp://host:9999?timeout=1000&username=user`, ``}, + {`ig://user:pass@localhost:9999/?timeout=1000`, `ignite`, `tcp://localhost:9999?password=pass&timeout=1000&username=user`, ``}, + {`ig://user:pass@localhost:9999/dbname?timeout=1000`, `ignite`, `tcp://localhost:9999/dbname?password=pass&timeout=1000&username=user`, ``}, + + {`sf://user@host:9999/dbname/schema?timeout=1000`, `snowflake`, `user@host:9999/dbname/schema?timeout=1000`, ``}, + {`sf://user:pass@localhost:9999/dbname/schema?timeout=1000`, `snowflake`, `user:pass@localhost:9999/dbname/schema?timeout=1000`, ``}, + + {`rs://user:pass@amazon.com/dbname`, `postgres`, `postgres://user:pass@amazon.com:5439/dbname`, ``}, // 67 + + {`ve://user:pass@vertica-host/dbvertica?tlsmode=server-strict`, `vertica`, `vertica://user:pass@vertica-host:5433/dbvertica?tlsmode=server-strict`, ``}, // 68 + + {`moderncsqlite:///path/to/file.sqlite3`, `moderncsqlite`, `/path/to/file.sqlite3`, ``}, + {`modernsqlite:///path/to/file.sqlite3`, `moderncsqlite`, `/path/to/file.sqlite3`, ``}, + {`mq://path/to/file.sqlite3`, `moderncsqlite`, `path/to/file.sqlite3`, ``}, + {`mq:path/to/file.sqlite3`, `moderncsqlite`, `path/to/file.sqlite3`, ``}, + {`mq:./path/to/file.sqlite3`, `moderncsqlite`, `./path/to/file.sqlite3`, ``}, + {`mq://./path/to/file.sqlite3?loc=auto`, `moderncsqlite`, `./path/to/file.sqlite3?loc=auto`, ``}, + {`mq::memory:?loc=auto`, `moderncsqlite`, `:memory:?loc=auto`, ``}, + {`mq://:memory:?loc=auto`, `moderncsqlite`, `:memory:?loc=auto`, ``}, + } + for i, test := range tests { + u, err := Parse(test.s) + if err != nil { + t.Errorf("test %d expected no error, got: %v", i, err) + continue + } + if u.Driver != test.d { + t.Errorf("test %d expected driver `%s`, got: `%s`", i, test.d, u.Driver) + } + if u.DSN != test.exp { + _, err := os.Stat(test.path) + if test.path != "" && err != nil && os.IsNotExist(err) { + t.Logf("test %d expected DSN `%s`, got: `%s` -- ignoring because `%s` does not exist", i, test.exp, u.DSN, test.path) + } else { + t.Errorf("test %d expected DSN `%s`, got: `%s`", i, test.exp, u.DSN) + } + } + } +} diff --git a/traffic_ops/vendor/github.com/xo/dburl/dsn.go b/traffic_ops/vendor/github.com/xo/dburl/dsn.go new file mode 100644 index 0000000000..98989b8a40 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/dsn.go @@ -0,0 +1,513 @@ +package dburl + +import ( + "net/url" + stdpath "path" + "strings" +) + +// GenScheme returns a func that generates a scheme:// style DSN from the +// passed URL. +func GenScheme(scheme string) func(*URL) (string, error) { + return func(u *URL) (string, error) { + z := &url.URL{ + Scheme: scheme, + Opaque: u.Opaque, + User: u.User, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + RawQuery: u.RawQuery, + Fragment: u.Fragment, + } + return z.String(), nil + } +} + +// GenFromURL returns a func that generates a DSN using urlstr as the default +// URL parameters, overriding the values only if when in the passed URL. +func GenFromURL(urlstr string) func(*URL) (string, error) { + z, err := url.Parse(urlstr) + if err != nil { + panic(err) + } + return func(u *URL) (string, error) { + opaque := z.Opaque + if u.Opaque != "" { + opaque = u.Opaque + } + user := z.User + if u.User != nil { + user = u.User + } + host, port := z.Hostname(), z.Port() + if h := u.Hostname(); h != "" { + host = h + } + if p := u.Port(); p != "" { + port = p + } + if port != "" { + host += ":" + port + } + path := z.Path + if u.Path != "" { + path = u.Path + } + rawPath := z.RawPath + if u.RawPath != "" { + rawPath = u.RawPath + } + q := z.Query() + for k, v := range u.Query() { + q.Set(k, strings.Join(v, " ")) + } + fragment := z.Fragment + if u.Fragment != "" { + fragment = u.Fragment + } + y := &url.URL{ + Scheme: z.Scheme, + Opaque: opaque, + User: user, + Host: host, + Path: path, + RawPath: rawPath, + RawQuery: q.Encode(), + Fragment: fragment, + } + return y.String(), nil + } +} + +// GenOpaque generates a opaque file path DSN from the passed URL. +func GenOpaque(u *URL) (string, error) { + if u.Opaque == "" { + return "", ErrMissingPath + } + return u.Opaque + genQueryOptions(u.Query()), nil +} + +// GenPostgres generates a postgres DSN from the passed URL. +func GenPostgres(u *URL) (string, error) { + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + if host == "." { + return "", ErrRelativePathNotSupported + } + // resolve path + if u.Proto == "unix" { + if host == "" { + dbname = "/" + dbname + } + host, port, dbname = resolveDir(stdpath.Join(host, dbname)) + } + // build q + q := u.Query() + q.Set("host", host) + q.Set("port", port) + q.Set("dbname", dbname) + // add user/pass + if u.User != nil { + q.Set("user", u.User.Username()) + pass, _ := u.User.Password() + q.Set("password", pass) + } + // save host, port, dbname + if u.hostPortDB == nil { + u.hostPortDB = []string{host, port, dbname} + } + return genOptions(q, "", "=", " ", ",", true), nil +} + +// GenSQLServer generates a mssql DSN from the passed URL. +func GenSQLServer(u *URL) (string, error) { + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + // add instance name to host if present + if i := strings.Index(dbname, "/"); i != -1 { + host = host + `\` + dbname[:i] + dbname = dbname[i+1:] + } + // build q + q := u.Query() + q.Set("Server", host) + q.Set("Port", port) + q.Set("Database", dbname) + // add user/pass + if u.User != nil { + q.Set("User ID", u.User.Username()) + pass, _ := u.User.Password() + q.Set("Password", pass) + } + // save host, port, dbname + if u.hostPortDB == nil { + u.hostPortDB = []string{host, port, dbname} + } + return genOptionsODBC(q, true), nil +} + +// GenMySQL generates a mysql DSN from the passed URL. +func GenMySQL(u *URL) (string, error) { + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + // build dsn + var dsn string + if u.User != nil { + if n := u.User.Username(); n != "" { + if p, ok := u.User.Password(); ok { + n += ":" + p + } + dsn += n + "@" + } + } + // resolve path + if u.Proto == "unix" { + if host == "" { + dbname = "/" + dbname + } + host, dbname = resolveSocket(stdpath.Join(host, dbname)) + port = "" + } + // save host, port, dbname + if u.hostPortDB == nil { + u.hostPortDB = []string{host, port, dbname} + } + // if host or proto is not empty + if u.Proto != "unix" { + if host == "" { + host = "127.0.0.1" + } + if port == "" { + port = "3306" + } + } + if port != "" { + port = ":" + port + } + // add proto and database + dsn += u.Proto + "(" + host + port + ")" + "/" + dbname + return dsn + genQueryOptions(u.Query()), nil +} + +// GenMyMySQL generates a MyMySQL MySQL DSN from the passed URL. +func GenMyMySQL(u *URL) (string, error) { + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + // resolve path + if u.Proto == "unix" { + if host == "" { + dbname = "/" + dbname + } + host, dbname = resolveSocket(stdpath.Join(host, dbname)) + port = "" + } + // save host, port, dbname + if u.hostPortDB == nil { + u.hostPortDB = []string{host, port, dbname} + } + // if host or proto is not empty + if u.Proto != "unix" { + if host == "" { + host = "127.0.0.1" + } + if port == "" { + port = "3306" + } + } + if port != "" { + port = ":" + port + } + // build dsn + dsn := u.Proto + ":" + host + port + dsn += genOptions( + convertOptions(u.Query(), "true", ""), + ",", "=", ",", " ", false, + ) + dsn += "*" + dbname + if u.User != nil { + pass, _ := u.User.Password() + dsn += "/" + u.User.Username() + "/" + pass + } else if strings.HasSuffix(dsn, "*") { + dsn += "//" + } + return dsn, nil +} + +// GenOracle generates a Go Driver for Oracle (godror) DSN from the passed URL. +func GenOracle(u *URL) (string, error) { + // Easy Connect Naming method enables clients to connect to a database server + // without any configuration. Clients use a connect string for a simple TCP/IP + // address, which includes a host name and optional port and service name: + // CONNECT username[/password]@[//]host[:port][/service_name][:server][/instance_name] + host, port, service := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + // grab instance name from service name + var instance string + if i := strings.LastIndex(service, "/"); i != -1 { + instance, service = service[i+1:], service[:i] + } + // build dsn + dsn := host + if port != "" { + dsn += ":" + port + } + if u.User != nil { + if n := u.User.Username(); n != "" { + if p, ok := u.User.Password(); ok { + n += "/" + p + } + dsn = n + "@//" + dsn + } + } + if service != "" { + dsn += "/" + service + } + if instance != "" { + dsn += "/" + instance + } + return dsn, nil +} + +// GenFirebird generates a firebirdsql DSN from the passed URL. +func GenFirebird(u *URL) (string, error) { + z := &url.URL{ + User: u.User, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + RawQuery: u.RawQuery, + Fragment: u.Fragment, + } + return strings.TrimPrefix(z.String(), "//"), nil +} + +// GenADODB generates a adodb DSN from the passed URL. +func GenADODB(u *URL) (string, error) { + // grab data source + host, port := u.Hostname(), u.Port() + dsname, dbname := strings.TrimPrefix(u.Path, "/"), "" + if dsname == "" { + dsname = "." + } + // check if data source is not a path on disk + if mode(dsname) == 0 { + if i := strings.IndexAny(dsname, `\/`); i != -1 { + dbname = dsname[i+1:] + dsname = dsname[:i] + } + } + // build q + q := u.Query() + q.Set("Provider", host) + q.Set("Port", port) + q.Set("Data Source", dsname) + q.Set("Database", dbname) + if u.User != nil { + q.Set("User ID", u.User.Username()) + pass, _ := u.User.Password() + q.Set("Password", pass) + } + if u.hostPortDB == nil { + n := dsname + if dbname != "" { + n += "/" + dbname + } + u.hostPortDB = []string{host, port, n} + } + return genOptionsODBC(q, true), nil +} + +// GenODBC generates a odbc DSN from the passed URL. +func GenODBC(u *URL) (string, error) { + // save host, port, dbname + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + if u.hostPortDB == nil { + u.hostPortDB = []string{host, port, dbname} + } + // build q + q := u.Query() + q.Set("Driver", "{"+strings.Replace(u.Proto, "+", " ", -1)+"}") + q.Set("Server", host) + if port == "" { + proto := strings.ToLower(u.Proto) + switch { + case strings.Contains(proto, "mysql"): + q.Set("Port", "3306") + case strings.Contains(proto, "postgres"): + q.Set("Port", "5432") + case strings.Contains(proto, "db2") || strings.Contains(proto, "ibm"): + q.Set("ServiceName", "50000") + default: + q.Set("Port", "1433") + } + } else { + q.Set("Port", port) + } + q.Set("Database", dbname) + // add user/pass + if u.User != nil { + q.Set("UID", u.User.Username()) + p, _ := u.User.Password() + q.Set("PWD", p) + } + return genOptionsODBC(q, true), nil +} + +// GenOLEODBC generates a oleodbc DSN from the passed URL. +func GenOLEODBC(u *URL) (string, error) { + props, err := GenODBC(u) + if err != nil { + return "", nil + } + return `Provider=MSDASQL.1;Extended Properties="` + props + `"`, nil +} + +// GenClickhouse generates a clickhouse DSN from the passed URL. +func GenClickhouse(u *URL) (string, error) { + z := &url.URL{ + Scheme: "tcp", + Opaque: u.Opaque, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + RawQuery: u.RawQuery, + Fragment: u.Fragment, + } + if z.Port() == "" { + z.Host += ":9000" + } + // build q + q := z.Query() + if u.User != nil { + if user := u.User.Username(); len(user) > 0 { + q.Set("username", user) + } + if pass, ok := u.User.Password(); ok { + q.Set("password", pass) + } + } + z.RawQuery = q.Encode() + return z.String(), nil +} + +// GenVoltDB generates a VoltDB DSN from the passed URL. +func GenVoltDB(u *URL) (string, error) { + host, port := "localhost", "21212" + if h := u.Hostname(); h != "" { + host = h + } + if p := u.Port(); p != "" { + port = p + } + return host + ":" + port, nil +} + +// GenPresto generates a Presto DSN from the passed URL. +func GenPresto(u *URL) (string, error) { + z := &url.URL{ + Scheme: "http", + Opaque: u.Opaque, + User: u.User, + Host: u.Host, + RawQuery: u.RawQuery, + Fragment: u.Fragment, + } + // change to https + if strings.HasSuffix(u.OriginalScheme, "s") { + z.Scheme = "https" + } + // force user + if z.User == nil { + z.User = url.User("user") + } + // force host + if z.Host == "" { + z.Host = "localhost" + } + // force port + if z.Port() == "" { + if z.Scheme == "http" { + z.Host += ":8080" + } else if z.Scheme == "https" { + z.Host += ":8443" + } + } + // add parameters + q := z.Query() + dbname, schema := strings.TrimPrefix(u.Path, "/"), "" + if dbname == "" { + dbname = "default" + } else if i := strings.Index(dbname, "/"); i != -1 { + schema, dbname = dbname[i+1:], dbname[:i] + } + q.Set("catalog", dbname) + if schema != "" { + q.Set("schema", schema) + } + z.RawQuery = q.Encode() + return z.String(), nil +} + +// GenCassandra generates a cassandra DSN from the passed URL. +func GenCassandra(u *URL) (string, error) { + host, port, dbname := "localhost", "9042", strings.TrimPrefix(u.Path, "/") + if h := u.Hostname(); h != "" { + host = h + } + if p := u.Port(); p != "" { + port = p + } + q := u.Query() + // add user/pass + if u.User != nil { + q.Set("username", u.User.Username()) + if pass, _ := u.User.Password(); pass != "" { + q.Set("password", pass) + } + } + // add dbname + if dbname != "" { + q.Set("keyspace", dbname) + } + return host + ":" + port + genQueryOptions(q), nil +} + +// GenIgnite generates an ignite DSN from the passed URL. +func GenIgnite(u *URL) (string, error) { + host, port, dbname := "localhost", "10800", strings.TrimPrefix(u.Path, "/") + if h := u.Hostname(); h != "" { + host = h + } + if p := u.Port(); p != "" { + port = p + } + q := u.Query() + // add user/pass + if u.User != nil { + q.Set("username", u.User.Username()) + if pass, _ := u.User.Password(); pass != "" { + q.Set("password", pass) + } + } + // add dbname + if dbname != "" { + dbname = "/" + dbname + } + return "tcp://" + host + ":" + port + dbname + genQueryOptions(q), nil +} + +// GenSnowflake generates a snowflake DSN from the passed URL. +func GenSnowflake(u *URL) (string, error) { + host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/") + if host == "" { + return "", ErrMissingHost + } + if port != "" { + port = ":" + port + } + // add user/pass + if u.User == nil { + return "", ErrMissingUser + } + user := u.User.Username() + if pass, _ := u.User.Password(); pass != "" { + user += ":" + pass + } + return user + "@" + host + port + "/" + dbname + genQueryOptions(u.Query()), nil +} diff --git a/traffic_ops/vendor/github.com/xo/dburl/go.mod b/traffic_ops/vendor/github.com/xo/dburl/go.mod new file mode 100644 index 0000000000..fa16722c06 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/go.mod @@ -0,0 +1,3 @@ +module github.com/xo/dburl + +go 1.15 diff --git a/traffic_ops/vendor/github.com/xo/dburl/scheme.go b/traffic_ops/vendor/github.com/xo/dburl/scheme.go new file mode 100644 index 0000000000..3eeee15d55 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/scheme.go @@ -0,0 +1,237 @@ +package dburl + +import ( + "fmt" + "sort" +) + +// Proto are the allowed transport protocol types in a database URL scheme. +type Proto uint + +// Proto types. +const ( + ProtoNone Proto = 0 + ProtoTCP Proto = 1 + ProtoUDP Proto = 2 + ProtoUnix Proto = 4 + ProtoAny Proto = 8 +) + +// Scheme wraps information used for registering a URL scheme with +// Parse/Open. +type Scheme struct { + // Driver is the name of the SQL driver that will set as the Scheme in + // Parse'd URLs, and is the driver name expected by the standard sql.Open + // calls. + // + // Note: a 2 letter alias will always be registered for the Driver as the + // first 2 characters of the Driver, unless one of the Aliases includes an + // alias that is 2 characters. + Driver string + + // Generator is the func responsible for generating a DSN based on parsed + // URL information. + // + // Note: this func should not modify the passed URL. + Generator func(*URL) (string, error) + + // Proto are allowed protocol types for the scheme. + Proto Proto + + // Opaque toggles Parse to not re-process URLs with an "opaque" component. + Opaque bool + + // Aliases are any additional aliases for the scheme. + Aliases []string + + // Override is the Go SQL driver to use instead of Driver. + Override string +} + +// BaseSchemes returns the supported base schemes. +func BaseSchemes() []Scheme { + return []Scheme{ + // core databases + {"mssql", GenSQLServer, 0, false, []string{"sqlserver"}, ""}, + {"mysql", GenMySQL, ProtoTCP | ProtoUDP | ProtoUnix, false, []string{"mariadb", "maria", "percona", "aurora"}, ""}, + {"godror", GenOracle, 0, false, []string{"or", "ora", "oracle", "oci", "oci8", "odpi", "odpi-c"}, ""}, + {"postgres", GenPostgres, ProtoUnix, false, []string{"pg", "postgresql", "pgsql"}, ""}, + {"sqlite3", GenOpaque, 0, true, []string{"sqlite", "file"}, ""}, + + // wire compatibles + {"cockroachdb", GenFromURL("postgres://localhost:26257/?sslmode=disable"), 0, false, []string{"cr", "cockroach", "crdb", "cdb"}, "postgres"}, + {"memsql", GenMySQL, 0, false, nil, "mysql"}, + {"redshift", GenFromURL("postgres://localhost:5439/"), 0, false, []string{"rs"}, "postgres"}, + {"tidb", GenMySQL, 0, false, nil, "mysql"}, + {"vitess", GenMySQL, 0, false, []string{"vt"}, "mysql"}, + + // alternate implementations + {"mymysql", GenMyMySQL, ProtoTCP | ProtoUDP | ProtoUnix, false, []string{"zm", "mymy"}, ""}, + {"pgx", GenScheme("postgres"), ProtoUnix, false, []string{"px"}, ""}, + + // other databases + {"adodb", GenADODB, 0, false, []string{"ado"}, ""}, + {"avatica", GenFromURL("http://localhost:8765/"), 0, false, []string{"phoenix"}, ""}, + {"clickhouse", GenClickhouse, 0, false, []string{"ch"}, ""}, + {"cql", GenCassandra, 0, false, []string{"ca", "cassandra", "datastax", "scy", "scylla"}, ""}, + {"firebirdsql", GenFirebird, 0, false, []string{"fb", "firebird"}, ""}, + {"hdb", GenScheme("hdb"), 0, false, []string{"sa", "saphana", "sap", "hana"}, ""}, + {"ignite", GenIgnite, 0, false, []string{"ig", "gridgain"}, ""}, + {"moderncsqlite", GenOpaque, 0, true, []string{"mq", "modernsqlite"}, ""}, + {"n1ql", GenFromURL("http://localhost:9000/"), 0, false, []string{"couchbase"}, ""}, + {"odbc", GenODBC, ProtoAny, false, nil, ""}, + {"oleodbc", GenOLEODBC, ProtoAny, false, []string{"oo", "ole"}, "adodb"}, + {"presto", GenPresto, 0, false, []string{"prestodb", "prestos", "prs", "prestodbs"}, ""}, + {"ql", GenOpaque, 0, true, []string{"ql", "cznic", "cznicql"}, ""}, + {"snowflake", GenSnowflake, 0, false, []string{"sf"}, ""}, + {"tds", GenFromURL("http://localhost:5000/"), 0, false, []string{"ax", "ase", "sapase"}, ""}, + {"vertica", GenFromURL("vertica://localhost:5433/"), 0, false, nil, ""}, + {"voltdb", GenVoltDB, 0, false, []string{"volt", "vdb"}, ""}, + } +} + +func init() { + // register schemes + schemes := BaseSchemes() + schemeMap = make(map[string]*Scheme, len(schemes)) + for _, scheme := range schemes { + Register(scheme) + } +} + +// schemeMap is the map of registered schemes. +var schemeMap map[string]*Scheme + +// registerAlias registers a alias for an already registered Scheme. +func registerAlias(name, alias string, doSort bool) { + scheme, ok := schemeMap[name] + if !ok { + panic(fmt.Sprintf("scheme %s not registered", name)) + } + if doSort && has(scheme.Aliases, alias) { + panic(fmt.Sprintf("scheme %s already has alias %s", name, alias)) + } + if _, ok := schemeMap[alias]; ok { + panic(fmt.Sprintf("scheme %s already registered", alias)) + } + scheme.Aliases = append(scheme.Aliases, alias) + if doSort { + sort.Slice(scheme.Aliases, func(i, j int) bool { + if len(scheme.Aliases[i]) <= len(scheme.Aliases[j]) { + return true + } + if len(scheme.Aliases[j]) < len(scheme.Aliases[i]) { + return false + } + return scheme.Aliases[i] < scheme.Aliases[j] + }) + } + schemeMap[alias] = scheme +} + +// Register registers a Scheme. +func Register(scheme Scheme) { + if scheme.Generator == nil { + panic("must specify Generator when registering Scheme") + } + if scheme.Opaque && scheme.Proto&ProtoUnix != 0 { + panic("scheme must support only Opaque or Unix protocols, not both") + } + // check if registered + if _, ok := schemeMap[scheme.Driver]; ok { + panic(fmt.Sprintf("scheme %s already registered", scheme.Driver)) + } + sz := &Scheme{ + Driver: scheme.Driver, + Generator: scheme.Generator, + Proto: scheme.Proto, + Opaque: scheme.Opaque, + Override: scheme.Override, + } + schemeMap[scheme.Driver] = sz + // add aliases + var hasShort bool + for _, alias := range scheme.Aliases { + if len(alias) == 2 { + hasShort = true + } + if scheme.Driver != alias { + registerAlias(scheme.Driver, alias, false) + } + } + if !hasShort && len(scheme.Driver) > 2 { + registerAlias(scheme.Driver, scheme.Driver[:2], false) + } + // ensure always at least one alias, and that if Driver is 2 characters, + // that it gets added as well + if len(sz.Aliases) == 0 || len(scheme.Driver) == 2 { + sz.Aliases = append(sz.Aliases, scheme.Driver) + } + // sort + sort.Slice(sz.Aliases, func(i, j int) bool { + if len(sz.Aliases[i]) <= len(sz.Aliases[j]) { + return true + } + if len(sz.Aliases[j]) < len(sz.Aliases[i]) { + return false + } + return sz.Aliases[i] < sz.Aliases[j] + }) +} + +// Unregister unregisters a Scheme and all associated aliases. +func Unregister(name string) *Scheme { + scheme, ok := schemeMap[name] + if ok { + for _, alias := range scheme.Aliases { + delete(schemeMap, alias) + } + delete(schemeMap, name) + return scheme + } + return nil +} + +// RegisterAlias registers a alias for an already registered Scheme.h +func RegisterAlias(name, alias string) { + registerAlias(name, alias, true) +} + +// has is a util func to determine if a contains b. +func has(a []string, b string) bool { + for _, s := range a { + if s == b { + return true + } + } + return false +} + +// SchemeDriverAndAliases returns the registered driver and aliases for a +// database scheme. +func SchemeDriverAndAliases(name string) (string, []string) { + if scheme, ok := schemeMap[name]; ok { + driver := scheme.Driver + if scheme.Override != "" { + driver = scheme.Override + } + var aliases []string + for _, alias := range scheme.Aliases { + if alias == driver { + continue + } + aliases = append(aliases, alias) + } + sort.Slice(aliases, func(i, j int) bool { + if len(aliases[i]) <= len(aliases[j]) { + return true + } + if len(aliases[j]) < len(aliases[i]) { + return false + } + return aliases[i] < aliases[j] + }) + return driver, aliases + } + return "", nil +} diff --git a/traffic_ops/vendor/github.com/xo/dburl/url.go b/traffic_ops/vendor/github.com/xo/dburl/url.go new file mode 100644 index 0000000000..321d8ece96 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/url.go @@ -0,0 +1,202 @@ +package dburl + +import ( + "net/url" + "strings" +) + +// URL wraps the standard net/url.URL type, adding OriginalScheme, Proto, +// Driver, and DSN strings. +type URL struct { + // URL is the base net/url/URL. + url.URL + + // OriginalScheme is the original parsed scheme (ie, "sq", "mysql+unix", "sap", etc). + OriginalScheme string + + // Proto is the specified protocol (ie, "tcp", "udp", "unix"), if provided. + Proto string + + // Driver is the non-aliased SQL driver name that should be used in a call + // to sql/Open. + Driver string + + // Unaliased is the unaliased driver name. + Unaliased string + + // DSN is the built connection "data source name" that can be used in a + // call to sql/Open. + DSN string + + // hostPortDB will be set by Gen*() funcs after determining the host, port, + // database. + // + // when empty, indicates that these values are not special, and can be + // retrieved as the host, port, and path[1:] as usual. + hostPortDB []string +} + +// Parse parses urlstr, returning a URL with the OriginalScheme, Proto, Driver, +// Unaliased, and DSN fields populated. +// +// Note: if urlstr has a Opaque component (ie, URLs not specified as "scheme://" +// but "scheme:"), and the database scheme does not support opaque components, +// then Parse will attempt to re-process the URL as "scheme://" using +// the OriginalScheme. +func Parse(urlstr string) (*URL, error) { + // parse url + u, err := url.Parse(urlstr) + if err != nil { + return nil, err + } + if u.Scheme == "" { + return nil, ErrInvalidDatabaseScheme + } + // create url + v := &URL{URL: *u, OriginalScheme: urlstr[:len(u.Scheme)], Proto: "tcp"} + // check for +protocol in scheme + var checkProto bool + if i := strings.IndexRune(v.Scheme, '+'); i != -1 { + v.Proto = urlstr[i+1 : len(u.Scheme)] + v.Scheme = v.Scheme[:i] + checkProto = true + } + // get dsn generator + scheme, ok := schemeMap[v.Scheme] + if !ok { + return nil, ErrUnknownDatabaseScheme + } + // if scheme does not understand opaque URLs, retry parsing after making a fully + // qualified URL + if !scheme.Opaque && v.Opaque != "" { + q := "" + if v.RawQuery != "" { + q = "?" + v.RawQuery + } + f := "" + if v.Fragment != "" { + f = "#" + v.Fragment + } + + return Parse(v.OriginalScheme + "://" + v.Opaque + q + f) + } + if scheme.Opaque && v.Opaque == "" { + // force Opaque + v.Opaque, v.Host, v.Path, v.RawPath = v.Host+v.Path, "", "", "" + } else if v.Host == "." || (v.Host == "" && strings.TrimPrefix(v.Path, "/") != "") { + // force unix proto + v.Proto = "unix" + } + // check proto + if checkProto || v.Proto != "tcp" { + if scheme.Proto == ProtoNone { + return nil, ErrInvalidTransportProtocol + } + switch { + case scheme.Proto&ProtoAny != 0 && v.Proto != "", + scheme.Proto&ProtoTCP != 0 && v.Proto == "tcp", + scheme.Proto&ProtoUDP != 0 && v.Proto == "udp", + scheme.Proto&ProtoUnix != 0 && v.Proto == "unix": + default: + return nil, ErrInvalidTransportProtocol + } + } + // set driver + v.Driver, v.Unaliased = scheme.Driver, scheme.Driver + if scheme.Override != "" { + v.Driver = scheme.Override + } + // generate dsn + v.DSN, err = scheme.Generator(v) + if err != nil { + return nil, err + } + return v, nil +} + +// String satisfies the stringer interface. +func (u *URL) String() string { + p := &url.URL{ + Scheme: u.OriginalScheme, + Opaque: u.Opaque, + User: u.User, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + RawQuery: u.RawQuery, + Fragment: u.Fragment, + } + return p.String() +} + +// Short provides a short description of the user, host, and database. +func (u *URL) Short() string { + if u.Scheme == "" { + return "" + } + s := schemeMap[u.Scheme].Aliases[0] + if u.Scheme == "odbc" || u.Scheme == "oleodbc" { + n := u.Proto + if v, ok := schemeMap[n]; ok { + n = v.Aliases[0] + } + s += "+" + n + } else if u.Proto != "tcp" { + s += "+" + u.Proto + } + s += ":" + if u.User != nil { + if n := u.User.Username(); n != "" { + s += n + "@" + } + } + if u.Host != "" { + s += u.Host + } + if u.Path != "" && u.Path != "/" { + s += u.Path + } + if u.Opaque != "" { + s += u.Opaque + } + return s +} + +// Normalize returns the driver, host, port, database, and user name of a URL, +// joined with sep, populating blank fields with empty. +func (u *URL) Normalize(sep, empty string, cut int) string { + s := []string{u.Unaliased, "", "", "", ""} + if u.Proto != "tcp" && u.Proto != "unix" { + s[0] += "+" + u.Proto + } + // set host port dbname fields + if u.hostPortDB == nil { + if u.Opaque != "" { + u.hostPortDB = []string{u.Opaque, "", ""} + } else { + u.hostPortDB = []string{u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/")} + } + } + copy(s[1:], u.hostPortDB) + // set user + if u.User != nil { + s[4] = u.User.Username() + } + // replace blank entries ... + for i := 0; i < len(s); i++ { + if s[i] == "" { + s[i] = empty + } + } + if cut > 0 { + // cut to only populated fields + i := len(s) - 1 + for ; i > cut; i-- { + if s[i] != "" { + break + } + } + s = s[:i] + } + return strings.Join(s, sep) +} diff --git a/traffic_ops/vendor/github.com/xo/dburl/util.go b/traffic_ops/vendor/github.com/xo/dburl/util.go new file mode 100644 index 0000000000..21b37a22f9 --- /dev/null +++ b/traffic_ops/vendor/github.com/xo/dburl/util.go @@ -0,0 +1,133 @@ +package dburl + +import ( + "net/url" + "os" + stdpath "path" + "sort" + "strings" +) + +// genOptions takes URL values and generates options, joining together with +// joiner, and separated by sep, with any multi URL values joined by valSep, +// ignoring any values with keys in ignore. +// +// For example, to build a "ODBC" style connection string, use like the following: +// genOptions(u.Query(), "", "=", ";", ",") +func genOptions(q url.Values, joiner, assign, sep, valSep string, skipWhenEmpty bool, ignore ...string) string { + qlen := len(q) + if qlen == 0 { + return "" + } + // make ignore map + ig := make(map[string]bool, len(ignore)) + for _, v := range ignore { + ig[strings.ToLower(v)] = true + } + // sort keys + s := make([]string, len(q)) + var i int + for k := range q { + s[i] = k + i++ + } + sort.Strings(s) + var opts []string + for _, k := range s { + if !ig[strings.ToLower(k)] { + val := strings.Join(q[k], valSep) + if !skipWhenEmpty || val != "" { + if val != "" { + val = assign + val + } + opts = append(opts, k+val) + } + } + } + if len(opts) != 0 { + return joiner + strings.Join(opts, sep) + } + return "" +} + +// genOptionsODBC is a util wrapper around genOptions that uses the fixed settings +// for ODBC style connection strings. +func genOptionsODBC(q url.Values, skipWhenEmpty bool, ignore ...string) string { + return genOptions(q, "", "=", ";", ",", skipWhenEmpty, ignore...) +} + +// genQueryOptions generates standard query options. +func genQueryOptions(q url.Values) string { + if s := q.Encode(); s != "" { + return "?" + s + } + return "" +} + +// convertOptions converts an option value based on name, value pairs. +func convertOptions(q url.Values, pairs ...string) url.Values { + n := make(url.Values) + for k, v := range q { + x := make([]string, len(v)) + for i, z := range v { + for j := 0; j < len(pairs); j += 2 { + if pairs[j] == z { + z = pairs[j+1] + } + } + x[i] = z + } + n[k] = x + } + return n +} + +// mode returns the mode of the path. +func mode(path string) os.FileMode { + if fi, err := os.Stat(path); err == nil { + return fi.Mode() + } + return 0 +} + +// resolveSocket tries to resolve a path to a Unix domain socket based on the +// form "/path/to/socket/dbname" returning either the original path and the +// empty string, or the components "/path/to/socket" and "dbname", when +// /path/to/socket/dbname is reported by os.Stat as a socket. +// +// Used for MySQL DSNs. +func resolveSocket(path string) (string, string) { + dir, dbname := path, "" + for dir != "" && dir != "/" && dir != "." { + if m := mode(dir); m&os.ModeSocket != 0 { + return dir, dbname + } + dir, dbname = stdpath.Dir(dir), stdpath.Base(dir) + } + return path, "" +} + +// resolveDir resolves a directory with a :port list. +// +// Used for PostgreSQL DSNs. +func resolveDir(path string) (string, string, string) { + dir := path + for dir != "" && dir != "/" && dir != "." { + port := "" + i, j := strings.LastIndex(dir, ":"), strings.LastIndex(dir, "/") + if i != -1 && i > j { + port = dir[i+1:] + dir = dir[:i] + } + if mode(dir)&os.ModeDir != 0 { + rest := strings.TrimPrefix(strings.TrimPrefix(strings.TrimPrefix(path, dir), ":"+port), "/") + return dir, port, rest + } + if j != -1 { + dir = dir[:j] + } else { + dir = "" + } + } + return path, "", "" +} diff --git a/traffic_ops/vendor/vendor.json b/traffic_ops/vendor/vendor.json index 34bc76d68d..67c7aedcc7 100644 --- a/traffic_ops/vendor/vendor.json +++ b/traffic_ops/vendor/vendor.json @@ -242,6 +242,12 @@ "revision": "dd1fe2071026ce53f36a39112e645b4d4f5793a4", "revisionTime": "2017-07-07T05:36:02Z" }, + { + "checksumSHA1": "QaEwwora8eI7iAOgTmgNJ/VTD0Y=", + "path": "github.com/xo/dburl", + "revision": "652e0d5720a3413f00f715f127ce54913e11af91", + "revisionTime": "2020-09-10T01:14:26Z" + }, { "checksumSHA1": "yYtETfWutYEFfQBRdJaCoR94OCw=", "path": "gopkg.in/DATA-DOG/go-sqlmock.v1", @@ -273,5 +279,5 @@ "revisionTime": "2017-02-21T15:59:18Z" } ], - "rootPath": "github.com/apache/trafficcontrol/traffic_ops" + "rootPath": "github.com/trafficcontrol/traffic_ops" }