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"
}