diff --git a/front50-sql/front50-sql.gradle b/front50-sql/front50-sql.gradle new file mode 100644 index 000000000..4a9dc7b46 --- /dev/null +++ b/front50-sql/front50-sql.gradle @@ -0,0 +1,17 @@ +apply from: "$rootDir/gradle/kotlin.gradle" + +dependencies { + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.2.1" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.2.1" + + implementation "com.netflix.spinnaker.front50:front50-core:${front50Version}" + + implementation "com.netflix.spinnaker.kork:kork-exceptions" + implementation "com.netflix.spinnaker.kork:kork-sql" + implementation "io.github.resilience4j:resilience4j-retry" + + runtime "mysql:mysql-connector-java:8.0.12" + + testImplementation "com.netflix.spinnaker.kork:kork-sql-test" +} diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/CompositeStorageServiceConfiguration.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/CompositeStorageServiceConfiguration.kt new file mode 100644 index 000000000..9881dadd8 --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/CompositeStorageServiceConfiguration.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.config + +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.front50.migrations.StorageServiceMigrator +import com.netflix.spinnaker.front50.model.CompositeStorageService +import com.netflix.spinnaker.front50.model.StorageService +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary + +@Configuration +@EnableConfigurationProperties(StorageServiceMigratorConfigurationProperties::class) +class CompositeStorageServiceConfiguration() { + @Bean + @Primary + @ConditionalOnProperty("spinnaker.migration.compositeStorageService.enabled") + fun compositeStorageService(properties: StorageServiceMigratorConfigurationProperties, + storageServices: List) = + CompositeStorageService( + storageServices.first { it.javaClass.canonicalName.equals(properties.primaryClass) }, + storageServices.first { it.javaClass.canonicalName.equals(properties.previousClass) } + ) + + @Bean + @ConditionalOnProperty("spinnaker.migration.enabled") + fun storageServiceMigrator(registry: Registry, + properties: StorageServiceMigratorConfigurationProperties, + storageServices: List) = + StorageServiceMigrator( + registry, + storageServices.first { it.javaClass.canonicalName.equals(properties.primaryClass) }, + storageServices.first { it.javaClass.canonicalName.equals(properties.previousClass) } + ) +} + +@ConfigurationProperties("spinnaker.migration") +data class StorageServiceMigratorConfigurationProperties( + var primaryClass: String? = null, + var previousClass: String? = null +) diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/SqlConfiguration.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/SqlConfiguration.kt new file mode 100644 index 000000000..8abe99898 --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/config/SqlConfiguration.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.config + +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.front50.config.CommonStorageServiceDAOConfig +import com.netflix.spinnaker.front50.model.SqlStorageService +import com.netflix.spinnaker.kork.sql.config.DefaultSqlConfiguration +import com.netflix.spinnaker.kork.sql.config.SqlProperties +import org.jooq.DSLContext +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import java.time.Clock + +@Configuration +@ConditionalOnProperty("sql.enabled") +@Import(DefaultSqlConfiguration::class) +class SqlConfiguration : CommonStorageServiceDAOConfig() { + + @Bean + fun sqlStorageService(objectMapper: ObjectMapper, + registry: Registry, + jooq: DSLContext, + sqlProperties: SqlProperties): SqlStorageService = + SqlStorageService(objectMapper, registry, jooq, Clock.systemDefaultZone(), sqlProperties.retries) +} diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/migrations/StorageServiceMigrator.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/migrations/StorageServiceMigrator.kt new file mode 100644 index 000000000..ee1b45b2c --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/migrations/StorageServiceMigrator.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.front50.migrations + +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.front50.model.ObjectType +import com.netflix.spinnaker.front50.model.StorageService +import com.netflix.spinnaker.front50.model.Timestamped +import com.netflix.spinnaker.security.AuthenticatedRequest +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking +import org.slf4j.LoggerFactory +import org.slf4j.MDC +import org.springframework.scheduling.annotation.Scheduled +import kotlin.system.measureTimeMillis + +class StorageServiceMigrator( + private val registry: Registry, + private val target: StorageService, + private val source: StorageService +) { + + companion object { + private val log = LoggerFactory.getLogger(StorageServiceMigrator::class.java) + } + + var migratorObjectsId = registry.createId("storageServiceMigrator.objects") + + fun migrate(objectType: ObjectType) { + log.info("Migrating {}", objectType) + + val sourceObjectKeys = source.listObjectKeys(objectType) + val targetObjectKeys = target.listObjectKeys(objectType) + + val migratableObjectKeys = sourceObjectKeys.filter { e -> + /* + * A migratable object is one that: + * - does not exist in 'target' + * or + * - has been more recently modified in 'source' + */ + !targetObjectKeys.containsKey(e.key) || targetObjectKeys[e.key]!! < e.value + } + + if (migratableObjectKeys.isEmpty()) { + log.info( + "No objects to migrate (objectType: {}, sourceObjectCount: {}, targetObjectCount: {})", + objectType, + sourceObjectKeys.size, + targetObjectKeys.size + ) + + return + } + + val deferred = migratableObjectKeys.keys.map { key -> + GlobalScope.async { + try { + val maxObjectVersions = if (objectType == ObjectType.ENTITY_TAGS) { + // current thinking is that ENTITY_TAGS will be separately migrated due to their volume (10-100k+) + 1 + } else { + // the history api defaults to returning 20 records so its arguably unnecessary to migrate much more than that + 30 + } + + val objectVersions = mutableListOf() + + try { + objectVersions.addAll(source.listObjectVersions(objectType, key, maxObjectVersions)) + } catch (e: Exception) { + log.warn( + "Unable to list object versions (objectType: {}, objectKey: {}), reason: {}", + objectType, + key, + e.message + ) + + // we have a number of objects in our production bucket with broken permissions that prevent version lookups + // but can be fetched directly w/o versions + objectVersions.add(source.loadObject(objectType, key)) + } + + objectVersions.reversed().forEach { obj -> + try { + MDC.put(AuthenticatedRequest.Header.USER.header, obj.lastModifiedBy) + target.storeObject(objectType, key, obj) + registry.counter( + migratorObjectsId.withTag("objectType", objectType.name).withTag("success", true) + ).increment() + } catch (e: Exception) { + registry.counter( + migratorObjectsId.withTag("objectType", objectType.name).withTag("success", false) + ).increment() + + throw e + } finally { + MDC.remove(AuthenticatedRequest.Header.USER.header) + } + } + } catch (e: Exception) { + log.error("Unable to migrate (objectType: {}, objectKey: {})", objectType, key, e) + } + } + } + + val migrationDurationMs = measureTimeMillis { + runBlocking { + deferred.awaitAll() + } + } + + log.info( + "Migration of {} took {}ms (objectCount: {})", + objectType, + migrationDurationMs, + migratableObjectKeys.size + ) + } + + @Scheduled(fixedDelay = 60000) + fun migrate() { + val migrationDurationMs = measureTimeMillis { + ObjectType.values().forEach { + if (it != ObjectType.ENTITY_TAGS) { + // need an alternative/more performant strategy for entity tags + migrate(it) + } + } + } + + log.info("Migration complete in {}ms", migrationDurationMs) + } +} diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/CompositeStorageService.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/CompositeStorageService.kt new file mode 100644 index 000000000..515a20660 --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/CompositeStorageService.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.front50.model + +import org.slf4j.LoggerFactory + +class CompositeStorageService( + private val primary: StorageService, + private val previous: StorageService +) : StorageService { + + companion object { + private val log = LoggerFactory.getLogger(CompositeStorageService::class.java) + } + + override fun ensureBucketExists() { + primary.ensureBucketExists() + previous.ensureBucketExists() + } + + override fun supportsVersioning(): Boolean { + return primary.supportsVersioning() + } + + override fun loadObject(objectType: ObjectType?, objectKey: String?): T { + if (objectType == ObjectType.ENTITY_TAGS) { + return previous.loadObject(objectType, objectKey) + } + + try { + return primary.loadObject(objectType, objectKey) + } catch (e: Exception) { + log.error("{}.loadObject({}, {}) failed (primary)", primary.javaClass.simpleName, objectType, objectKey) + return previous.loadObject(objectType, objectKey) + } + } + + override fun deleteObject(objectType: ObjectType?, objectKey: String?) { + primary.deleteObject(objectType, objectKey) + previous.deleteObject(objectType, objectKey) + } + + override fun storeObject(objectType: ObjectType?, objectKey: String?, item: T) { + var exception: Exception? = null + + try { + primary.storeObject(objectType, objectKey, item) + } catch (e: Exception) { + exception = e + log.error( + "{}.storeObject({}, {}) failed", + primary.javaClass.simpleName, + objectType, + objectKey, + e + ) + } + + try { + previous.storeObject(objectType, objectKey, item) + } catch (e: Exception) { + exception = e + log.error( + "{}.storeObject({}, {}) failed", + previous.javaClass.simpleName, + objectType, + objectKey, + e + ) + } + + if (exception != null) { + throw exception + } + } + + override fun listObjectKeys(objectType: ObjectType?): Map { + val primaryObjectKeys = primary.listObjectKeys(objectType) + val previousObjectKeys = previous.listObjectKeys(objectType) + + return previousObjectKeys + primaryObjectKeys + } + + override fun listObjectVersions(objectType: ObjectType?, + objectKey: String?, + maxResults: Int): MutableCollection { + try { + return primary.listObjectVersions(objectType, objectKey, maxResults) + } catch (e: Exception) { + log.error( + "{}.listObjectVersions({}, {}, {}) failed (primary)", + primary.javaClass.simpleName, + objectType, + objectKey, + maxResults + ) + return previous.listObjectVersions(objectType, objectKey, maxResults) + } + } + + override fun getLastModified(objectType: ObjectType?): Long { + try { + return primary.getLastModified(objectType) + } catch (e: Exception) { + log.error("{}.getLastModified({}) failed (primary)", primary.javaClass.simpleName, objectType) + return previous.getLastModified(objectType) + } + } +} diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/SqlStorageService.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/SqlStorageService.kt new file mode 100644 index 000000000..cd60afa42 --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/SqlStorageService.kt @@ -0,0 +1,193 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.front50.model + +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spectator.api.Registry +import com.netflix.spinnaker.security.AuthenticatedRequest +import org.jooq.DSLContext +import org.jooq.impl.DSL +import org.jooq.impl.DSL.* +import org.jooq.util.mysql.MySQLDSL +import org.slf4j.LoggerFactory +import java.time.Clock + +import com.netflix.spinnaker.front50.model.ObjectType.* +import com.netflix.spinnaker.front50.model.sql.* +import com.netflix.spinnaker.kork.sql.config.SqlRetryProperties + +class SqlStorageService( + private val objectMapper: ObjectMapper, + private val registry: Registry, + private val jooq: DSLContext, + private val clock: Clock, + private val sqlRetryProperties: SqlRetryProperties +) : StorageService { + + companion object { + private val log = LoggerFactory.getLogger(SqlStorageService::class.java) + + private val definitionsByType = mutableMapOf( + PROJECT to ProjectTableDefinition(), + PIPELINE to PipelineTableDefinition(), + STRATEGY to PipelineStrategyTableDefinition(), + PIPELINE_TEMPLATE to DefaultTableDefinition(PIPELINE_TEMPLATE, "pipeline_templates", true), + NOTIFICATION to DefaultTableDefinition(NOTIFICATION, "notifications", true), + SERVICE_ACCOUNT to DefaultTableDefinition(SERVICE_ACCOUNT, "service_accounts", true), + APPLICATION to DefaultTableDefinition(APPLICATION, "applications", true), + APPLICATION_PERMISSION to DefaultTableDefinition(APPLICATION_PERMISSION, "application_permissions", true), + SNAPSHOT to DefaultTableDefinition(SNAPSHOT, "snapshots", false), + ENTITY_TAGS to DefaultTableDefinition(ENTITY_TAGS, "entity_tags", false), + DELIVERY to DeliveryTableDefinition() + ) + } + + override fun ensureBucketExists() { + // no-op + } + + override fun supportsVersioning(): Boolean { + return true + } + + override fun loadObject(objectType: ObjectType, objectKey: String): T { + val result = jooq.withRetry(sqlRetryProperties.reads) { ctx -> + ctx + .select(field("body", String::class.java)) + .from(definitionsByType[objectType]!!.tableName) + .where(field("id", String::class.java).eq(objectKey)) + .fetchOne() + } + + return objectMapper.readValue(result.get(field("body", String::class.java)), objectType.clazz as Class) + } + + override fun deleteObject(objectType: ObjectType, objectKey: String) { + jooq.transactional(sqlRetryProperties.transactions) { ctx -> + if (definitionsByType[objectType]!!.supportsHistory) { + ctx + .update(table(definitionsByType[objectType]!!.tableName)) + .set(DSL.field("is_deleted", Boolean::class.java), true) + .where(DSL.field("id", String::class.java).eq(objectKey)) + .execute() + } else { + ctx + .delete(table(definitionsByType[objectType]!!.tableName)) + .where(DSL.field("id", String::class.java).eq(objectKey)) + .execute() + } + } + } + + override fun storeObject(objectType: ObjectType, objectKey: String, item: T) { + item.lastModifiedBy = AuthenticatedRequest.getSpinnakerUser().orElse("anonymous") + + try { + jooq.transactional(sqlRetryProperties.transactions) { ctx -> + val insert = ctx.insertInto( + table(definitionsByType[objectType]!!.tableName), + definitionsByType[objectType]!!.getFields() + ) + + insert.apply { + values(definitionsByType[objectType]!!.getValues(objectMapper, objectKey, item)) + + onDuplicateKeyUpdate() + .set(field("body"), MySQLDSL.values(field("body")) as Any) + .set(field("last_modified_at"), MySQLDSL.values(field("last_modified_at")) as Any) + .set(field("last_modified_by"), MySQLDSL.values(field("last_modified_by")) as Any) + .set(field("is_deleted"), MySQLDSL.values(field("is_deleted")) as Any) + } + + insert.execute() + + if (definitionsByType[objectType]!!.supportsHistory) { + val historicalInsert = ctx.insertInto( + table(definitionsByType[objectType]!!.historyTableName), + definitionsByType[objectType]!!.getHistoryFields() + ) + + historicalInsert.apply { + values(definitionsByType[objectType]!!.getHistoryValues(objectMapper, clock, objectKey, item)) + + onDuplicateKeyIgnore() + } + + historicalInsert.execute() + } + } + } catch (e: Exception) { + log.error("Unable to store object (objectType: {}, objectKey: {})", objectType, objectKey) + throw e + } + } + + override fun listObjectKeys(objectType: ObjectType): Map { + val resultSet = jooq.withRetry(sqlRetryProperties.reads) { ctx -> + ctx + .select( + field("id", String::class.java), + field("last_modified_at", Long::class.java) + ) + .from(table(definitionsByType[objectType]!!.tableName)) + .where(DSL.field("is_deleted", Boolean::class.java).eq(false)) + .fetch() + .intoResultSet() + } + + val objectKeys = mutableMapOf() + + while (resultSet.next()) { + objectKeys.put(resultSet.getString(1), resultSet.getLong(2)) + } + + return objectKeys + } + + override fun listObjectVersions(objectType: ObjectType, + objectKey: String, + maxResults: Int): List { + val bodies = jooq.withRetry(sqlRetryProperties.reads) { ctx -> + ctx + .select(field("body", String::class.java)) + .from(definitionsByType[objectType]!!.tableName) + .where(DSL.field("id", String::class.java).eq(objectKey)) + .fetch() + .getValues(field("body", String::class.java)) + } + + return bodies.map { + objectMapper.readValue(it, objectType.clazz as Class) + } + } + + override fun getLastModified(objectType: ObjectType): Long { + val resultSet = jooq.withRetry(sqlRetryProperties.reads) { ctx -> + ctx + .select(max(field("last_modified_at", Long::class.java)).`as`("last_modified_at")) + .from(table(definitionsByType[objectType]!!.tableName)) + .fetch() + .intoResultSet() + } + + return if (resultSet.next()) { + return resultSet.getLong(1) + } else { + 0 + } + } +} diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/Jooq.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/Jooq.kt new file mode 100644 index 000000000..3a3c29bbe --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/Jooq.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.front50.model.sql + +import com.netflix.spinnaker.kork.core.RetrySupport +import com.netflix.spinnaker.kork.sql.config.RetryProperties +import org.jooq.DSLContext +import org.jooq.Record +import org.jooq.impl.DSL + +internal val retrySupport = RetrySupport() + +/** + * Run the provided [fn] in a transaction. + */ +internal fun DSLContext.transactional(retryProperties: RetryProperties, fn: (DSLContext) -> Unit) { + retrySupport.retry({ + transaction { ctx -> + fn(DSL.using(ctx)) + } + }, retryProperties.maxRetries, retryProperties.backoffMs, false) +} + +/** + * Run the provided [fn] with retry support. + */ +internal fun DSLContext.withRetry(retryProperties: RetryProperties, fn: (DSLContext) -> T): T { + return retrySupport.retry({ + fn(this) + }, retryProperties.maxRetries, retryProperties.backoffMs, false) +} + diff --git a/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/TableDefinitions.kt b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/TableDefinitions.kt new file mode 100644 index 000000000..6ae6d4e83 --- /dev/null +++ b/front50-sql/src/main/kotlin/com/netflix/spinnaker/front50/model/sql/TableDefinitions.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.spinnaker.front50.model.sql + +import com.fasterxml.jackson.databind.ObjectMapper +import com.google.common.hash.Hashing +import com.netflix.spinnaker.front50.model.ObjectType +import com.netflix.spinnaker.front50.model.Timestamped +import com.netflix.spinnaker.front50.model.delivery.Delivery +import com.netflix.spinnaker.front50.model.pipeline.Pipeline +import com.netflix.spinnaker.front50.model.project.Project +import org.jooq.Field +import org.jooq.impl.DSL +import java.nio.charset.StandardCharsets.UTF_8 +import java.time.Clock + +open class DefaultTableDefinition( + val objectType: ObjectType, + val tableName: String, + val supportsHistory: Boolean +) { + val historyTableName:String + get() = "${tableName}_history" + + open fun getFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("body"), + DSL.field("created_at"), + DSL.field("last_modified_at"), + DSL.field("last_modified_by"), + DSL.field("is_deleted") + ) + } + + open fun getValues(objectMapper: ObjectMapper, objectKey: String, item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + return listOf( + objectKey, objectAsString, item.lastModified, item.lastModified, item.lastModifiedBy, false + ) + } + + fun getHistoryFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("body"), + DSL.field("body_sig"), + DSL.field("last_modified_at"), + DSL.field("recorded_at") + ) + } + + fun getHistoryValues(objectMapper: ObjectMapper, + clock: Clock, + objectKey: String, + item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + + val signature = Hashing.murmur3_128().newHasher().putString(objectAsString, UTF_8).hash().toString() + + return listOf( + objectKey, objectAsString, signature, item.lastModified, clock.millis() + ) + } +} + +class DeliveryTableDefinition : DefaultTableDefinition(ObjectType.DELIVERY, "deliveries", true) { + override fun getFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("application"), + DSL.field("body"), + DSL.field("created_at"), + DSL.field("last_modified_at"), + DSL.field("last_modified_by"), + DSL.field("is_deleted") + ) + } + + override fun getValues(objectMapper: ObjectMapper, objectKey: String, item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + return listOf( + objectKey, + (item as Delivery).application, + objectAsString, + item.lastModified, + item.lastModified, + item.lastModifiedBy, + false + ) + } +} + +class PipelineTableDefinition : DefaultTableDefinition(ObjectType.PIPELINE, "pipelines", true) { + override fun getFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("name"), + DSL.field("application"), + DSL.field("body"), + DSL.field("created_at"), + DSL.field("last_modified_at"), + DSL.field("last_modified_by"), + DSL.field("is_deleted") + ) + } + + override fun getValues(objectMapper: ObjectMapper, objectKey: String, item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + return listOf( + objectKey, + (item as Pipeline).name, + (item as Pipeline).application, + objectAsString, + item.lastModified, + item.lastModified, + item.lastModifiedBy, + false + ) + } +} + +class PipelineStrategyTableDefinition : DefaultTableDefinition(ObjectType.STRATEGY, "pipeline_strategies", true) { + override fun getFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("name"), + DSL.field("application"), + DSL.field("body"), + DSL.field("created_at"), + DSL.field("last_modified_at"), + DSL.field("last_modified_by"), + DSL.field("is_deleted") + ) + } + + override fun getValues(objectMapper: ObjectMapper, objectKey: String, item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + return listOf( + objectKey, + (item as Pipeline).name, + (item as Pipeline).application, + objectAsString, + item.lastModified, + item.lastModified, + item.lastModifiedBy, + false + ) + } +} + +class ProjectTableDefinition : DefaultTableDefinition(ObjectType.PROJECT, "projects", true) { + override fun getFields(): List> { + return listOf( + DSL.field("id"), + DSL.field("name"), + DSL.field("body"), + DSL.field("created_at"), + DSL.field("last_modified_at"), + DSL.field("last_modified_by"), + DSL.field("is_deleted") + ) + } + + override fun getValues(objectMapper: ObjectMapper, objectKey: String, item: Timestamped): List { + val objectAsString = objectMapper.writeValueAsString(item) + return listOf( + objectKey, + (item as Project).name, + objectAsString, + item.lastModified, + item.lastModified, + item.lastModifiedBy, + false + ) + } +} diff --git a/front50-sql/src/main/resources/db/changelog-master.yml b/front50-sql/src/main/resources/db/changelog-master.yml new file mode 100644 index 000000000..4a40e1008 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog-master.yml @@ -0,0 +1,34 @@ +databaseChangeLog: + - include: + file: changelog/20190415-initial-application-permissions-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-applications-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-deliveries-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-entity-tags-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-notifications-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-pipeline-strategies-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-pipeline-templates-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-pipelines-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-projects-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-service-accounts-schema.yml + relativeToChangelogFile: true + - include: + file: changelog/20190415-initial-snapshots-schema.yml + relativeToChangelogFile: true diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-application-permissions-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-application-permissions-schema.yml new file mode 100644 index 000000000..c563469bb --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-application-permissions-schema.yml @@ -0,0 +1,89 @@ +databaseChangeLog: + - changeSet: + id: create-application-permissions-table + author: ajordens + changes: + - createTable: + tableName: application_permissions + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: applications + - changeSet: + id: create-application-permissions-history-table + author: ajordens + changes: + - createTable: + tableName: application_permissions_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: application_permissions_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-applications-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-applications-schema.yml new file mode 100644 index 000000000..38239a8f1 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-applications-schema.yml @@ -0,0 +1,89 @@ +databaseChangeLog: + - changeSet: + id: create-applications-table + author: ajordens + changes: + - createTable: + tableName: applications + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: applications + - changeSet: + id: create-applications-history-table + author: ajordens + changes: + - createTable: + tableName: applications_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: applications_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-deliveries-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-deliveries-schema.yml new file mode 100644 index 000000000..c82156094 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-deliveries-schema.yml @@ -0,0 +1,94 @@ +databaseChangeLog: + - changeSet: + id: create-deliveries-table + author: ajordens + changes: + - createTable: + tableName: deliveries + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: application + type: char(255) + constraints: + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: deliveries + - changeSet: + id: create-deliveries-history-table + author: ajordens + changes: + - createTable: + tableName: deliveries_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: deliveries_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-entity-tags-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-entity-tags-schema.yml new file mode 100644 index 000000000..b09d4825e --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-entity-tags-schema.yml @@ -0,0 +1,47 @@ +databaseChangeLog: + - changeSet: + id: create-entity-tags-table + author: ajordens + changes: + - createTable: + tableName: entity_tags + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: entity_tags diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-notifications-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-notifications-schema.yml new file mode 100644 index 000000000..124a950c3 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-notifications-schema.yml @@ -0,0 +1,89 @@ +databaseChangeLog: + - changeSet: + id: create-notifications-table + author: ajordens + changes: + - createTable: + tableName: notifications + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: notifications + - changeSet: + id: create-notifications-history-table + author: ajordens + changes: + - createTable: + tableName: notifications_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: notifications_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-strategies-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-strategies-schema.yml new file mode 100644 index 000000000..0c109cca3 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-strategies-schema.yml @@ -0,0 +1,99 @@ +databaseChangeLog: + - changeSet: + id: create-pipeline-strategies-table + author: ajordens + changes: + - createTable: + tableName: pipeline_strategies + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: name + type: char(255) + constraints: + nullable: false + - column: + name: application + type: char(255) + constraints: + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipeline_strategies + - changeSet: + id: create-pipeline-strategies-history-table + author: ajordens + changes: + - createTable: + tableName: pipeline_strategies_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipeline_strategies_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-templates-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-templates-schema.yml new file mode 100644 index 000000000..07b91c7e8 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipeline-templates-schema.yml @@ -0,0 +1,89 @@ +databaseChangeLog: + - changeSet: + id: create-pipeline-templates-table + author: ajordens + changes: + - createTable: + tableName: pipeline_templates + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipeline_templates + - changeSet: + id: create-pipeline-templates-history-table + author: ajordens + changes: + - createTable: + tableName: pipeline_templates_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipeline_templates_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-pipelines-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipelines-schema.yml new file mode 100644 index 000000000..de6505fd9 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-pipelines-schema.yml @@ -0,0 +1,99 @@ +databaseChangeLog: + - changeSet: + id: create-pipelines-table + author: ajordens + changes: + - createTable: + tableName: pipelines + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: name + type: char(255) + constraints: + nullable: false + - column: + name: application + type: char(255) + constraints: + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipelines + - changeSet: + id: create-pipelines-history-table + author: ajordens + changes: + - createTable: + tableName: pipelines_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: pipelines_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-projects-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-projects-schema.yml new file mode 100644 index 000000000..168d98ca4 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-projects-schema.yml @@ -0,0 +1,94 @@ +databaseChangeLog: + - changeSet: + id: create-projects-table + author: ajordens + changes: + - createTable: + tableName: projects + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: name + type: char(255) + constraints: + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: projects + - changeSet: + id: create-projects-history-table + author: ajordens + changes: + - createTable: + tableName: projects_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: projects_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-service-accounts-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-service-accounts-schema.yml new file mode 100644 index 000000000..34ef305af --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-service-accounts-schema.yml @@ -0,0 +1,89 @@ +databaseChangeLog: + - changeSet: + id: create-service-accounts-table + author: ajordens + changes: + - createTable: + tableName: service_accounts + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: service_accounts + - changeSet: + id: create-service-accounts-history-table + author: ajordens + changes: + - createTable: + tableName: service_accounts_history + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: body_sig + type: char(32) + constraints: + primaryKey: true + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + primaryKey: true + nullable: false + - column: + name: recorded_at + type: bigint + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: service_accounts_history diff --git a/front50-sql/src/main/resources/db/changelog/20190415-initial-snapshots-schema.yml b/front50-sql/src/main/resources/db/changelog/20190415-initial-snapshots-schema.yml new file mode 100644 index 000000000..7d480aea8 --- /dev/null +++ b/front50-sql/src/main/resources/db/changelog/20190415-initial-snapshots-schema.yml @@ -0,0 +1,47 @@ +databaseChangeLog: + - changeSet: + id: create-snapshots-table + author: ajordens + changes: + - createTable: + tableName: snapshots + columns: + - column: + name: id + type: char(255) + constraints: + primaryKey: true + nullable: false + - column: + name: body + type: longtext + constraints: + nullable: false + - column: + name: created_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_at + type: bigint + constraints: + nullable: false + - column: + name: last_modified_by + type: varchar(255) + constraints: + nullable: false + - column: + name: is_deleted + type: boolean + defaultValueBoolean: false + constraints: + nullable: false + - modifySql: + dbms: mysql + append: + value: " engine innodb DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci" + rollback: + - dropTable: + tableName: snapshots diff --git a/front50-sql/src/main/resources/db/database-mysql.sql b/front50-sql/src/main/resources/db/database-mysql.sql new file mode 100644 index 000000000..4fbc63e88 --- /dev/null +++ b/front50-sql/src/main/resources/db/database-mysql.sql @@ -0,0 +1 @@ +CREATE SCHEMA `front50` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ; diff --git a/front50-sql/src/main/resources/db/my.cnf b/front50-sql/src/main/resources/db/my.cnf new file mode 100644 index 000000000..8a4ea2d6b --- /dev/null +++ b/front50-sql/src/main/resources/db/my.cnf @@ -0,0 +1,13 @@ +[client] +default-character-set = utf8mb4 + +[mysql] +default-character-set = utf8mb4 + +[mysqld] +character-set-client-handshake = FALSE +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# Only allow connections from localhost +bind-address = 127.0.0.1 diff --git a/gradle/kotlin.gradle b/gradle/kotlin.gradle new file mode 100644 index 000000000..f9f5b1ad5 --- /dev/null +++ b/gradle/kotlin.gradle @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * 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. + */ + +apply plugin: "nebula.kotlin" +apply plugin: "kotlin-spring" + +configurations.all { + resolutionStrategy { + eachDependency { details -> + if (details.requested.group == "org.jetbrains.kotlin") { + details.useVersion "1.3.10" + } + } + } +} + +compileKotlin { + kotlinOptions { + languageVersion = "1.3" + jvmTarget = "1.8" + } +}