From f4eef40ece8cc62e2f08d55449cadd3a4fedc4b8 Mon Sep 17 00:00:00 2001 From: Sanjay Vasandani Date: Fri, 6 May 2022 11:24:21 -0700 Subject: [PATCH] Add tool library for updating Spanner schema using Liquibase. This updates Spanner dependencies to latest versions, including a workaround for grpc/grpc-java#9149. --- .../measurement/gcloud/spanner/BUILD.bazel | 14 +++- .../spanner/SpannerDatabaseConnector.kt | 21 +++++ .../gcloud/spanner/SpannerFlags.kt | 21 +---- .../gcloud/spanner/tools/BUILD.bazel | 15 ++++ .../gcloud/spanner/tools/UpdateSchema.kt | 76 +++++++++++++++++++ 5 files changed, 125 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/BUILD.bazel create mode 100644 src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/UpdateSchema.kt diff --git a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/BUILD.bazel index 5c79da09b..9c72d3ece 100644 --- a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/BUILD.bazel +++ b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/BUILD.bazel @@ -4,13 +4,23 @@ load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") package(default_visibility = ["//visibility:public"]) +kt_jvm_library( + name = "flags", + srcs = ["SpannerFlags.kt"], + deps = ["//imports/java/picocli"], +) + kt_jvm_library( name = "spanner", - srcs = glob(["*.kt"]), + srcs = glob( + ["*.kt"], + exclude = ["SpannerFlags.kt"], + ), + exports = [":flags"], resources = ["//src/main/resources/META-INF/services:grpclb"], deps = [ + ":flags", "//imports/java/com/google/cloud/spanner", - "//imports/java/picocli", "//imports/kotlin/kotlinx/coroutines:core", "//src/main/kotlin/org/wfanet/measurement/common", "//src/main/kotlin/org/wfanet/measurement/common/identity", diff --git a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerDatabaseConnector.kt b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerDatabaseConnector.kt index 781baaca1..bba94c6a0 100644 --- a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerDatabaseConnector.kt +++ b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerDatabaseConnector.kt @@ -78,3 +78,24 @@ class SpannerDatabaseConnector( private val logger = Logger.getLogger(this::class.java.name) } } + +/** Builds a [SpannerDatabaseConnector] from these flags. */ +private fun SpannerFlags.toSpannerDatabaseConnector(): SpannerDatabaseConnector { + return SpannerDatabaseConnector( + projectName = projectName, + instanceName = instanceName, + databaseName = databaseName, + readyTimeout = readyTimeout, + emulatorHost = emulatorHost + ) +} + +/** + * Executes [block] with a [SpannerDatabaseConnector] resource once it's ready, ensuring that the + * resource is closed. + */ +suspend fun SpannerFlags.usingSpanner( + block: suspend (spanner: SpannerDatabaseConnector) -> R +): R { + return toSpannerDatabaseConnector().usingSpanner(block) +} diff --git a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerFlags.kt b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerFlags.kt index fcc3b09fd..5322f1cde 100644 --- a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerFlags.kt +++ b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/SpannerFlags.kt @@ -56,25 +56,6 @@ class SpannerFlags { description = ["Host name and port of the spanner emulator."], required = false ) - var spannerEmulatorHost: String? = null + var emulatorHost: String? = null private set - - /** Builds a [SpannerDatabaseConnector] from these flags. */ - private fun toSpannerDatabaseConnector(): SpannerDatabaseConnector { - return SpannerDatabaseConnector( - projectName = projectName, - instanceName = instanceName, - databaseName = databaseName, - readyTimeout = readyTimeout, - emulatorHost = spannerEmulatorHost - ) - } - - /** - * Executes [block] with a [SpannerDatabaseConnector] resource once it's ready, ensuring that the - * resource is closed. - */ - suspend fun usingSpanner(block: suspend (spanner: SpannerDatabaseConnector) -> R): R { - return toSpannerDatabaseConnector().usingSpanner(block) - } } diff --git a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/BUILD.bazel b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/BUILD.bazel new file mode 100644 index 000000000..96af52b56 --- /dev/null +++ b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") + +package(default_visibility = ["//visibility:public"]) + +kt_jvm_library( + name = "update_schema", + srcs = ["UpdateSchema.kt"], + resources = ["//src/main/resources/META-INF/services:grpclb"], + runtime_deps = ["//imports/java/liquibase/ext/spanner"], + deps = [ + "//src/main/kotlin/org/wfanet/measurement/common", + "//src/main/kotlin/org/wfanet/measurement/common/db/liquibase", + "//src/main/kotlin/org/wfanet/measurement/gcloud/spanner:flags", + ], +) diff --git a/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/UpdateSchema.kt b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/UpdateSchema.kt new file mode 100644 index 000000000..457788e2c --- /dev/null +++ b/src/main/kotlin/org/wfanet/measurement/gcloud/spanner/tools/UpdateSchema.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Cross-Media Measurement Authors + * + * 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. + */ + +package org.wfanet.measurement.gcloud.spanner.tools + +import java.sql.DriverManager +import java.util.logging.Level +import java.util.logging.Logger +import liquibase.Contexts +import liquibase.Scope +import org.wfanet.measurement.common.commandLineMain +import org.wfanet.measurement.common.db.liquibase.Liquibase +import org.wfanet.measurement.common.db.liquibase.setLogLevel +import org.wfanet.measurement.common.getJarResourcePath +import org.wfanet.measurement.gcloud.spanner.SpannerFlags +import picocli.CommandLine.Command +import picocli.CommandLine.Mixin +import picocli.CommandLine.Option + +@Command +class UpdateSchema : Runnable { + @Mixin private lateinit var flags: SpannerFlags + + @Option( + names = ["--changelog"], + description = ["Liquibase changelog resource name"], + required = true, + ) + private lateinit var changelog: String + + override fun run() { + val connectionString = + if (flags.emulatorHost == null) { + "jdbc:cloudspanner:/projects/${flags.projectName}/instances/${flags.instanceName}/" + + "databases/${flags.databaseName}" + } else { + "jdbc:cloudspanner://${flags.emulatorHost}/projects/${flags.projectName}/instances/" + + "${flags.instanceName}/databases/${flags.databaseName}" + + ";usePlainText=true;autoConfigEmulator=true" + } + + val changelogPath = + checkNotNull(Thread.currentThread().contextClassLoader.getJarResourcePath(changelog)) { + "JAR resource $changelog not found" + } + + logger.info("Connecting to $connectionString") + DriverManager.getConnection(connectionString).use { connection -> + logger.info("Loading changelog from $changelogPath") + Liquibase.fromPath(connection, changelogPath).use { liquibase -> + logger.info("Updating...") + Scope.getCurrentScope().setLogLevel(Level.FINE) + liquibase.update(Contexts()) + } + } + } + + companion object { + private val logger = Logger.getLogger(this::class.java.name) + + @JvmStatic fun main(args: Array) = commandLineMain(UpdateSchema(), args) + } +}