google-nomulus/db/build.gradle
Weimin Yu 959c7f7899 Enhance the test for forbidden Schema changes (#815)
* Enhance the test for forbidden Schema changes

Current test is git-based. It is difficult to maintain and does not
catch out-of-order version numbers. It is also more aggressive than
necessary, failing on changes to submitted scripts that have not been
deployed yet.

The new test starts a database, deploys the current schema to it,
then deploys the set of Flyway scripts in this repository to the database.
2020-09-24 12:31:08 -04:00

223 lines
7 KiB
Groovy

// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
plugins {
id "org.flywaydb.flyway" version "6.0.1"
id 'maven-publish'
}
ext {
Set restrictedDbEnv =
[ 'sandbox', 'production' ].asUnmodifiable()
Set allDbEnv =
[ 'alpha', 'crash' ].plus(restrictedDbEnv).asUnmodifiable()
def dbServerProperty = 'dbServer'
def dbNameProperty = 'dbName'
def dbServer = findProperty(dbServerProperty).toString().toLowerCase()
def dbName = findProperty(dbNameProperty)
isRestricted = {
return restrictedDbEnv.contains(dbServer)
}
getAccessInfoByHostPort = { hostAndPort ->
println "Database set to ${hostAndPort}."
return [
url: "jdbc:postgresql://${hostAndPort}/${dbName}",
user: findProperty('dbUser'),
password: findProperty('dbPassword')]
}
getSocketFactoryAccessInfo = { env ->
def cred = getCloudSqlCredential(env, 'admin').split(' ')
def sqlInstance = cred[0]
println "Database set to Cloud SQL instance ${sqlInstance}."
return [
url: """\
jdbc:postgresql://google/${dbName}?cloudSqlInstance=
${sqlInstance}&socketFactory=
com.google.cloud.sql.postgres.SocketFactory"""
.stripIndent()
.replaceAll(System.lineSeparator(), '') ,
user: cred[1],
password: cred[2]]
}
getJdbcAccessInfo = {
if (allDbEnv.contains(dbServer)) {
return getSocketFactoryAccessInfo(dbServer)
} else if (!dbServer.isEmpty()) {
return getAccessInfoByHostPort(dbServer)
} else {
// Not connecting to a database. Return a dummy object for Flyway config.
return [ url: '', user: '', password: '' ]
}
}
// Retrieves Cloud SQL credential for a given role. Result is in the form of
// 'instancename username password'.
//
// The env parameter may be one of the following: alpha, crash, sandbox, or
// production. The role parameter may be superuser. (More roles will be added
// later).
getCloudSqlCredential = { env, role ->
def devProject = project.hasProperty('devProject')
? project.getProperty('devProject') : rootProject.devProject
def gcpProject = project.hasProperty('gcpProject')
? project.getProperty('gcpProject') : rootProject.gcpProject
def keyProject = env in restrictedDbEnv? devProject : gcpProject
def command =
"""gsutil cp \
gs://${gcpProject}-beam/cloudsql/${role}_credential.enc - | \
base64 -d | \
gcloud kms decrypt --location global --keyring nomulus-tool-keyring \
--key nomulus-tool-key --plaintext-file=- \
--ciphertext-file=- \
--project=${keyProject}"""
return execInBash(command, '/tmp')
}
}
task schemaJar(type: Jar) {
archiveBaseName = 'schema'
from(sourceSets.main.resources) {
include 'sql/flyway/**'
include 'sql/schema/nomulus.golden.sql'
}
}
// Expose NomulusPostgreSql class to ':core' for compile, without leaking
// unnecessary dependencies to the release artifacts through ':core'.
// Jar is put in the 'compileApi' configuration.
task compileApiJar(type: Jar) {
archiveBaseName = 'compile'
from(sourceSets.main.output) {
include 'google/registry/persistence/NomulusPostgreSql**'
}
}
configurations {
compileApi
schema
integration
}
artifacts {
compileApi compileApiJar
schema schemaJar
}
publishing {
repositories {
maven {
url project.publish_repo
}
}
publications {
sqlSchemaPublication(MavenPublication) {
groupId 'google.registry'
artifactId 'schema'
version project.schema_version
artifact schemaJar
}
}
}
flyway {
def accessInfo = project.ext.getJdbcAccessInfo()
url = accessInfo.url
user = accessInfo.user
password = accessInfo.password
schemas = [ 'public' ]
locations = [ "classpath:sql/flyway" ]
}
dependencies {
def deps = rootProject.dependencyMap
compile deps['org.flywaydb:flyway-core']
runtimeOnly deps['com.google.cloud.sql:postgres-socket-factory']
runtimeOnly deps['org.postgresql:postgresql']
testCompile deps['com.google.flogger:flogger']
testRuntime deps['com.google.flogger:flogger-system-backend']
testCompile deps['com.google.guava:guava']
testCompile deps['com.google.truth:truth']
testRuntime deps['io.github.java-diff-utils:java-diff-utils']
testCompile deps['org.junit.jupiter:junit-jupiter-api']
testCompile deps['org.junit.jupiter:junit-jupiter-engine']
testCompile deps['org.testcontainers:postgresql']
testCompile deps['org.testcontainers:testcontainers']
testCompile deps['org.testcontainers:junit-jupiter']
testCompile deps['org.testcontainers:postgresql']
testCompile project(path: ':common', configuration: 'testing')
}
flywayInfo.dependsOn('buildNeeded')
flywayValidate.dependsOn('buildNeeded')
if (ext.isRestricted()) {
// Disable dangerous Flyway tasks in sandbox and production. Only allow info and validate.
tasks.findAll { task -> task.group.equals('Flyway')}.each {
if (it.name == 'flywayMigrate') {
it.doFirst {
throw new UnsupportedOperationException(
""" \
FlywayMigrate is disabled. See README.md for schema deployment
instructions.""".stripIndent())
}
} else if (it.name != 'flywayInfo' && it.name != 'flywayValidate') {
it.doFirst {
throw new UnsupportedOperationException(
"${it.name} from commandline is not allowed.")
}
}
}
}
if (project.baseSchemaTag != '') {
repositories {
maven {
url project.publish_repo
}
}
dependencies {
integration "google.registry:schema:${project.baseSchemaTag}"
}
// Checks if Flyway scripts can be deployed to an existing database with
// an older release. Please refer to SchemaTest.java for more information.
task schemaIncrementalDeployTest(dependsOn: processResources, type: Test) {
useJUnitPlatform()
include 'google/registry/sql/flyway/SchemaTest.*'
classpath = configurations.testRuntimeClasspath
.plus(configurations.integration)
.plus(files(sourceSets.test.output.classesDirs))
.plus(files(sourceSets.test.output.resourcesDir))
.plus(files(sourceSets.main.output.classesDirs))
// Declare test-runtime dependency on Flyway scripts in the resources dir.
// They are not on classpath since they conflict with the base schema.
inputs.dir sourceSets.main.output.resourcesDir
// Specifies which test to run using the following property
systemProperty 'deploy_to_existing_db', 'true'
}
}