diff --git a/go.mod b/go.mod index 15a548c06f..c3a1ec38b5 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cockroachdb/examples-orms go 1.13 require ( - github.com/cockroachdb/cockroach-go/v2 v2.0.6 + github.com/cockroachdb/cockroach-go/v2 v2.0.7 github.com/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec // indirect github.com/go-pg/pg/v9 v9.1.6 github.com/go-sql-driver/mysql v1.5.0 // indirect diff --git a/go.sum b/go.sum index 8a54c4ac71..7e58a7c316 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.0.6 h1:7JKIGouLq/hP+rxHIZ1emZ0J3AXPt8DE7yU/xw8JZPc= -github.com/cockroachdb/cockroach-go/v2 v2.0.6/go.mod h1:nkf7rUmgPdawp3EwRjXIumihI2AYg9usGNWbJ2hsJqI= +github.com/cockroachdb/cockroach-go/v2 v2.0.7-0.20200903114400-4bf206d91533 h1:FrTWuMCYMkwme38Us67F6VK5xSF1/iLlasSJR8EIhIg= +github.com/cockroachdb/cockroach-go/v2 v2.0.7-0.20200903114400-4bf206d91533/go.mod h1:nkf7rUmgPdawp3EwRjXIumihI2AYg9usGNWbJ2hsJqI= +github.com/cockroachdb/cockroach-go/v2 v2.0.7 h1:Xkv9PbnvBrZjxRdW7l6GLz76bxAE/xaro+SGTyLz6gQ= +github.com/cockroachdb/cockroach-go/v2 v2.0.7/go.mod h1:nkf7rUmgPdawp3EwRjXIumihI2AYg9usGNWbJ2hsJqI= github.com/codemodus/kace v0.5.1 h1:4OCsBlE2c/rSJo375ggfnucv9eRzge/U5LrrOZd47HA= github.com/codemodus/kace v0.5.1/go.mod h1:coddaHoX1ku1YFSe4Ip0mL9kQjJvKkzb9CfIdG1YR04= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -187,6 +189,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f h1:ESK9Jb5JOE+y4u+ozMQeXfMHwEHm6zVbaDQkeaj6wI4= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testing/main_test.go b/testing/main_test.go index f21f1af1bc..b1c60e07f4 100644 --- a/testing/main_test.go +++ b/testing/main_test.go @@ -47,14 +47,19 @@ type tenantServer interface { } // newServer creates a new cockroachDB server. -func newServer(t *testing.T, insecure bool) testserver.TestServer { +func newServer(t *testing.T, auth authMode) testserver.TestServer { t.Helper() var ts testserver.TestServer var err error - if insecure { - ts, err = testserver.NewTestServer() - } else { + switch auth { + case authClientCert: ts, err = testserver.NewTestServer(testserver.SecureOpt()) + case authPassword: + ts, err = testserver.NewTestServer(testserver.SecureOpt(), testserver.RootPasswordOpt("hunter2")) + case authInsecure: + ts, err = testserver.NewTestServer() + default: + err = fmt.Errorf("unknown authMode %d", auth) } if err != nil { t.Fatal(err) @@ -64,9 +69,9 @@ func newServer(t *testing.T, insecure bool) testserver.TestServer { // newTenant creates a new SQL Tenant pointed at the given TestServer. See // TestServer.NewTenantServer for more information. -func newTenant(t *testing.T, ts testserver.TestServer) testserver.TestServer { +func newTenant(t *testing.T, ts testserver.TestServer, proxy bool) testserver.TestServer { t.Helper() - tenant, err := ts.(tenantServer).NewTenantServer(false /* proxy */) + tenant, err := ts.(tenantServer).NewTenantServer(proxy) if err != nil { t.Fatal(err) } @@ -193,17 +198,39 @@ var minRequiredVersionsByORMName = map[string]struct { }, } +type authMode byte + +const ( + // Use client certs. When testing tenants, does not use the proxy (as the proxy does not support client certs). + authClientCert authMode = iota + // Use password auth. When testing tenants, tests through a proxy. + authPassword + // Use --insecure. When testing tenants, does not use the proxy (as the proxy does not support insecure connections). + authInsecure + + authModeSentinel // sentinel to iterate over all modes +) + +func (mode authMode) String() string { + switch mode { + case authClientCert: + return "client-cert" + case authPassword: + return "password" + case authInsecure: + return "insecure" + default: + return "unknown" + } +} + type testInfo struct { language, orm string tableNames testTableNames // defaults to defaultTestTableNames columnNames testColumnNames // defaults to defaultTestColumnNames - // insecure is set if ORM does not handle secure servers (client certs). - // In that case, we start an insecure server (and don't test in tenant - // mode). - insecure bool } -func testORM(t *testing.T, info testInfo) { +func testORM(t *testing.T, info testInfo, auth authMode) { if info.tableNames == (testTableNames{}) { info.tableNames = defaultTestTableNames } @@ -222,7 +249,7 @@ func testORM(t *testing.T, info testInfo) { } var testCases []testCase { - ts := newServer(t, info.insecure) + ts := newServer(t, auth) db, dbURL, stopDB := startServerWithApplication(t, ts, app) defer stopDB() @@ -268,11 +295,19 @@ FROM t.Fatalf("unable to read cluster version: %s", err) } if tenantsSupported { - tenant := newTenant(t, ts) + // Connect to the tenant through the SQL proxy, which is only supported + // when using secure+password auth. (The proxy does not support client + // certs or insecure connections). + proxySupported := auth == authPassword + name := "RegularTenant" + if proxySupported { + name += "ThroughProxy" + } + tenant := newTenant(t, ts, proxySupported) db, dbURL, stopDB := startServerWithApplication(t, tenant, app) defer stopDB() testCases = append(testCases, testCase{ - name: "RegularTenant", + name: name, db: db, dbURL: dbURL, }) @@ -368,66 +403,111 @@ FROM } } +func testORMForAuthModesExcept(t *testing.T, info testInfo, skips map[authMode]string /* mode -> reason */) { + for auth := authMode(0); auth < authModeSentinel; auth++ { + t.Run(fmt.Sprint(auth), func(t *testing.T) { + if msg := skips[auth]; msg != "" { + t.Skip(msg) + } + testORM(t, info, auth) + }) + } +} + +func nothingSkipped() map[authMode]string { return nil } + func TestGORM(t *testing.T) { - testORM(t, testInfo{language: "go", orm: "gorm"}) + testORMForAuthModesExcept(t, testInfo{language: "go", orm: "gorm"}, nothingSkipped()) } func TestGOPG(t *testing.T) { - testORM(t, testInfo{ - language: "go", - orm: "gopg", - // GoPG does not support client certs: - // https://github.com/go-pg/pg/blob/v10/options.go - // If we set up a secure deployment and went through the proxy, it would work (or should anyway), but only - // via the 'database' parameter; GoPG also does not support the 'options' parameter. - insecure: true, - }) + testORMForAuthModesExcept(t, + testInfo{language: "go", orm: "gopg"}, + map[authMode]string{ + // https://github.com/go-pg/pg/blob/v10/options.go + // If we set up a secure deployment and went through the proxy, it would work (or should anyway), but only + // via the 'database' parameter; GoPG also does not support the 'options' parameter. + // + // pg: options other than 'sslmode', 'application_name' and 'connect_timeout' are not supported + authClientCert: "GoPG does not support custom root cert", + authPassword: "GoPG does not support custom root cert", + }) } func TestHibernate(t *testing.T) { - testORM(t, testInfo{ - language: "java", - orm: "hibernate", - // Possibly does not unescape the path correctly: - // Caused by: java.io.FileNotFoundException: - // %2Ftmp%2Fcockroach-testserver913095208%2Fcerts%2Fca.crt (No such file or directory) - insecure: true, - }) + testORMForAuthModesExcept(t, + testInfo{language: "java", orm: "hibernate"}, + map[authMode]string{ + // Driver does not unescape the path correctly: + // Caused by: java.io.FileNotFoundException: + // %2Ftmp%2Fcockroach-testserver913095208%2Fcerts%2Fca.crt (No such file or directory) + // + // Furthermore, if we preprocess the query string via + // + // tc.dbURL.RawQuery, err = url.QueryUnescape(tc.dbURL.RawQuery) + // + // then we run into + // https://github.com/dbeaver/dbeaver/issues/1835 + // because hibernate expects the key in DER format, but it is PEM. + authClientCert: "needs DER format and unescaped query string", + // Doesn't seem to understand connection strings. + // Caused by: java.net.UnknownHostException: root:hunter2@localhost + authPassword: "needs custom setup for password support", + }, + ) } func TestSequelize(t *testing.T) { - testORM(t, testInfo{ - language: "node", - orm: "sequelize", - // Requires bespoke code to actually use SSL, see: - // https://github.com/sequelize/sequelize/issues/10015 - insecure: true, - }) + testORMForAuthModesExcept(t, + testInfo{language: "node", orm: "sequelize"}, + map[authMode]string{ + // Requires bespoke code to actually use SSL, see: + // https://github.com/sequelize/sequelize/issues/10015 + authClientCert: "needs custom SSL setup", + authPassword: "needs custom SSL setup", + }, + ) } func TestSQLAlchemy(t *testing.T) { - testORM(t, testInfo{ - language: "python", - orm: "sqlalchemy", - }) + testORMForAuthModesExcept(t, testInfo{language: "python", orm: "sqlalchemy"}, nothingSkipped()) } func TestDjango(t *testing.T) { - testORM(t, testInfo{ + testORMForAuthModesExcept(t, testInfo{ language: "python", orm: "django", tableNames: djangoTestTableNames, columnNames: djangoTestColumnNames, - // No support for client certs (at least not via the query string). - // psycopg2.OperationalError: fe_sendauth: no password supplied - insecure: true, - }) + }, + map[authMode]string{ + // No support for client certs (at least not via the query string). + // psycopg2.OperationalError: fe_sendauth: no password supplied + authClientCert: "client certs via query string unsupported", + // Ditto, + // psycopg2.OperationalError: fe_sendauth: no password supplied + authPassword: "password via query string unsupported", + }, + ) } +// TODO(rafiss): why can't I run the ActiveRecords tests manually +// with this invocation? +// +// ./docker.sh make deps +// ./docker.sh go test -v -run ActiveRecord ./testing +// +// It always fails opaquely like this (after some normal-looking output): +// +// => Run `rails server -h` for more startup options +// Exiting +// make: *** [Makefile:23: start] Error 1 +// + func TestActiveRecord(t *testing.T) { - testORM(t, testInfo{language: "ruby", orm: "activerecord"}) + testORMForAuthModesExcept(t, testInfo{language: "ruby", orm: "activerecord"}, nothingSkipped()) } func TestActiveRecord4(t *testing.T) { - testORM(t, testInfo{language: "ruby", orm: "ar4"}) + testORMForAuthModesExcept(t, testInfo{language: "ruby", orm: "ar4"}, nothingSkipped()) }