Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,22 @@ jobs:

- name: Install conveyor
run: |
wget https://downloads.hydraulic.dev/conveyor/conveyor-18.0-linux-amd64.tar.gz
tar -xzf conveyor-18.0-linux-amd64.tar.gz
chmod +x conveyor-18.0/bin/conveyor
echo "$(pwd)/conveyor-18.0/bin" >> $GITHUB_PATH
wget https://downloads.hydraulic.dev/conveyor/conveyor-22.0-linux-amd64.tar.gz
tar -xzf conveyor-22.0-linux-amd64.tar.gz
chmod +x conveyor-22.0/bin/conveyor
echo "$(pwd)/conveyor-22.0/bin" >> $GITHUB_PATH

- name: Build and push with conveyor
uses: coactions/setup-xvfb@v1
with:
run: ./gradlew conveyUnixCI
run: |
for attempt in 1 2 3; do
echo "conveyUnixCI attempt ${attempt}/3"
./gradlew conveyUnixCI && exit 0
if [ "${attempt}" -lt 3 ]; then
echo "Conveyor failed (often Debian mirror timeout), retrying in 45s..."
sleep 45
fi
done
exit 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
Expand Down Expand Up @@ -141,10 +148,10 @@ jobs:

- name: Install conveyor
run: |
wget https://downloads.hydraulic.dev/conveyor/conveyor-18.0-linux-amd64.tar.gz
tar -xzf conveyor-18.0-linux-amd64.tar.gz
chmod +x conveyor-18.0/bin/conveyor
echo "$(pwd)/conveyor-18.0/bin" >> $GITHUB_PATH
wget https://downloads.hydraulic.dev/conveyor/conveyor-22.0-linux-amd64.tar.gz
tar -xzf conveyor-22.0-linux-amd64.tar.gz
chmod +x conveyor-22.0/bin/conveyor
echo "$(pwd)/conveyor-22.0/bin" >> $GITHUB_PATH

- name: Build and push with conveyor
uses: coactions/setup-xvfb@v1
Expand Down
29 changes: 18 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,22 @@ jobs:

- name: Install conveyor
run: |
wget https://downloads.hydraulic.dev/conveyor/conveyor-18.0-linux-amd64.tar.gz
tar -xzf conveyor-18.0-linux-amd64.tar.gz
chmod +x conveyor-18.0/bin/conveyor
echo "$(pwd)/conveyor-18.0/bin" >> $GITHUB_PATH
wget https://downloads.hydraulic.dev/conveyor/conveyor-22.0-linux-amd64.tar.gz
tar -xzf conveyor-22.0-linux-amd64.tar.gz
chmod +x conveyor-22.0/bin/conveyor
echo "$(pwd)/conveyor-22.0/bin" >> $GITHUB_PATH

- name: Build and push with conveyor
uses: coactions/setup-xvfb@v1
with:
run: ./gradlew conveyUnixCI
run: |
for attempt in 1 2 3; do
echo "conveyUnixCI attempt ${attempt}/3"
./gradlew conveyUnixCI && exit 0
if [ "${attempt}" -lt 3 ]; then
echo "Conveyor failed (often Debian mirror timeout), retrying in 45s..."
sleep 45
fi
done
exit 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
Expand Down Expand Up @@ -135,10 +142,10 @@ jobs:

- name: Install conveyor
run: |
wget https://downloads.hydraulic.dev/conveyor/conveyor-18.0-linux-amd64.tar.gz
tar -xzf conveyor-18.0-linux-amd64.tar.gz
chmod +x conveyor-18.0/bin/conveyor
echo "$(pwd)/conveyor-18.0/bin" >> $GITHUB_PATH
wget https://downloads.hydraulic.dev/conveyor/conveyor-22.0-linux-amd64.tar.gz
tar -xzf conveyor-22.0-linux-amd64.tar.gz
chmod +x conveyor-22.0/bin/conveyor
echo "$(pwd)/conveyor-22.0/bin" >> $GITHUB_PATH

- name: Build and push with conveyor
uses: coactions/setup-xvfb@v1
Expand Down
133 changes: 133 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.testing.Test

plugins {
alias(libs.plugins.kotlin.multiplatform).apply(false)
alias(libs.plugins.kotlin.serialization).apply(false)
Expand All @@ -8,6 +11,101 @@ plugins {
alias(libs.plugins.conveyor).apply(false)
}

/**
* hive-jdbc standalone embeds SLF4J 1.7.x; if its jar precedes slf4j-api 2.x on the classpath,
* LoggerFactory loads the shaded 1.x API and falls back to NOP (no StaticLoggerBinder).
*/
fun prioritizeSlf4jBindingsOnClasspath(files: Collection<File>): List<File> {
val (bindings, rest) = files.partition { file ->
val name = file.name
name.startsWith("slf4j-api-") ||
name.startsWith("logback-classic-") ||
name.startsWith("logback-core-")
}
val orderedBindings = bindings.sortedBy { file ->
when {
file.name.startsWith("slf4j-api-") -> 0
file.name.startsWith("logback-classic-") -> 1
file.name.startsWith("logback-core-") -> 2
else -> 3
}
}
return orderedBindings + rest
}

fun slf4jBindingPriority(classpathEntry: String): Int? = when {
"slf4j-api-" in classpathEntry -> 0
"logback-classic-" in classpathEntry -> 1
"logback-core-" in classpathEntry -> 2
else -> null
}

/** Reorders jar path lines inside Conveyor `app.inputs = [...]` blocks (plugin lists deps alphabetically). */
fun reorderConveyorJarInputBlocks(cfgFile: File) {
val lines = cfgFile.readLines()
val result = ArrayList<String>(lines.size)
var i = 0
while (i < lines.size) {
val line = lines[i]
val trimmed = line.trim()
if (trimmed.endsWith("[") && trimmed.contains(".inputs")) {
result.add(line)
i++
val jarLines = ArrayList<String>()
while (i < lines.size && !lines[i].trim().startsWith("]")) {
val current = lines[i]
if (current.trim().endsWith(".jar")) {
jarLines.add(current)
} else {
result.add(current)
}
i++
}
if (jarLines.isNotEmpty()) {
val indent = jarLines.first().takeWhile(Char::isWhitespace)
val ordered = jarLines
.map { it.trim() }
.sortedWith(compareBy({ slf4jBindingPriority(it) ?: 3 }, { it }))
.map { "$indent$it" }
result.addAll(ordered)
}
if (i < lines.size) {
result.add(lines[i])
}
i++
} else {
result.add(line)
i++
}
}
cfgFile.writeText(result.joinToString("\n", postfix = "\n"))
}

/** Reorders app.classpath lines in jpackage-generated .cfg (alphabetical order puts hive before slf4j-api). */
fun reorderJpackageAppClasspath(cfgFile: File) {
val lines = cfgFile.readLines()
if (lines.none { it.startsWith("app.classpath=") }) return

val sortedClasspath = lines
.filter { it.startsWith("app.classpath=") }
.sortedWith(compareBy({ slf4jBindingPriority(it) ?: 3 }, { it }))

val result = buildList(lines.size) {
var classpathWritten = false
for (line in lines) {
if (line.startsWith("app.classpath=")) {
if (!classpathWritten) {
addAll(sortedClasspath)
classpathWritten = true
}
} else {
add(line)
}
}
}
cfgFile.writeText(result.joinToString("\n", postfix = "\n"))
}

val dummyAttribute = Attribute.of("org.angryscan", String::class.java)

group = "org.angryscan"
Expand All @@ -17,7 +115,42 @@ subprojects {
group = rootProject.group
version = rootProject.version

tasks.withType<JavaExec>().configureEach {
doFirst {
classpath = files(prioritizeSlf4jBindingsOnClasspath(classpath.files))
}
}

tasks.withType<Test>().configureEach {
doFirst {
classpath = files(prioritizeSlf4jBindingsOnClasspath(classpath.files))
}
}

if (name == "desktop") {
tasks.matching {
it.name == "createDistributable" || it.name == "createReleaseDistributable"
}.configureEach {
doLast {
val appRoot = layout.buildDirectory.dir("compose/binaries/main/app").get().asFile
if (!appRoot.exists()) return@doLast
appRoot.walkTopDown().filter { it.isFile && it.extension == "cfg" }.forEach { cfg ->
reorderJpackageAppClasspath(cfg)
}
}
}

afterEvaluate {
tasks.named("fixConveyorConfig").configure {
doLast {
val configFile = layout.projectDirectory.file("generated.conveyor.conf").asFile
if (configFile.exists()) {
reorderConveyorJarInputBlocks(configFile)
}
}
}
}
}
}

dependencies {
Expand Down
2 changes: 1 addition & 1 deletion desktop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ tasks.register("fixConveyorConfig") {
tempFile.renameTo(inputFile)
println("Removed android/ios entries from generated.conveyor.conf")
}
dependsOn("build", "writeConveyorConfig")
dependsOn("assemble", "writeConveyorConfig")
}

tasks.register("printVersion") {
Expand Down
7 changes: 7 additions & 0 deletions desktop/conveyor.unix.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ app {
mac.aarch64
]

# Default archive.ubuntu.com often times out on GitHub Actions; use runner-local mirror first.
linux.debian.distribution.mirrors = [
"http://azure.archive.ubuntu.com/ubuntu/",
"http://archive.ubuntu.com/ubuntu/",
"http://mirrors.edge.kernel.org/ubuntu/",
]

build.gradle {
env = {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ internal class MainKtTest : KoinTest {
}
}
}
}
}
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ sql-mysql = { module = "com.mysql:mysql-connector-j", version = "9.1.0" }
sql-clickhouse = { module = "com.clickhouse:clickhouse-jdbc", version = "0.9.8" }
sql-redshift = { module = "com.amazon.redshift:redshift-jdbc42", version = "2.2.5" }
sql-mssql = { module = "com.microsoft.sqlserver:mssql-jdbc", version = "12.10.0.jre11" }
sql-mongodb = { module = "org.mongodb:mongodb-driver-sync", version = "5.2.1" }
sql-flyway = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }

ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
Expand Down
11 changes: 7 additions & 4 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ kotlin {
implementation(libs.sql.clickhouse)
implementation(libs.sql.redshift)
implementation(libs.sql.mssql)
// Uber-jar: thin hive-jdbc omits RPC/Thrift. Woodstox on classpath fixes
// META-INF/services XMLOutputFactory entries that reference com.ctc.wstx.*.
implementation("org.apache.hive:hive-jdbc:4.2.0:standalone@jar")
implementation("com.fasterxml.woodstox:woodstox-core:6.6.2")
implementation(libs.sql.mongodb)
implementation(libs.sql.flyway)

api(libs.exposed.core)
Expand Down Expand Up @@ -76,6 +73,12 @@ kotlin {
implementation(libs.logging.oshai)
implementation(libs.logging.logback)

// Uber-jar: thin hive-jdbc omits RPC/Thrift. Woodstox on classpath fixes
// META-INF/services XMLOutputFactory entries that reference com.ctc.wstx.*.
// Declared after logging so slf4j-api/logback precede hive's embedded SLF4J 1.7 on the classpath.
implementation("com.fasterxml.woodstox:woodstox-core:6.6.2")
implementation("org.apache.hive:hive-jdbc:4.2.0:standalone@jar")

api(libs.koin.core)
api(libs.koin.compose)
api(libs.koin.compose.viewmodel)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.angryscan.app.common
import kotlinx.serialization.Serializable

/**
* Supported SQL database types for scanning.
* Supported database types for scanning (relational SQL engines and MongoDB).
*/
@Serializable
enum class DatabaseType {
Expand All @@ -15,7 +15,8 @@ enum class DatabaseType {
CockroachDB,
ClickHouse,
Redshift,
SqlServer
SqlServer,
MongoDB,
}

/** Short label for type picker (sidebar, chips). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ private const val DefaultCockroachDBPort = 26257
private const val DefaultClickHousePort = 8123
private const val DefaultRedshiftPort = 5439
private const val DefaultSqlServerPort = 1433
private const val DefaultMongoPort = 27_017

/**
* Required connection fields for database types.
* - Server DBs (PostgreSQL, MySQL, GreenPlum, Hive, Redshift, Microsoft SQL Server, …): HOST, PORT, DATABASE, USER, PASSWORD
* - Server DBs (PostgreSQL, MySQL, …): HOST, PORT, DATABASE, USER, PASSWORD
* - MongoDB: HOST, PORT, DATABASE (user/password optional for unauthenticated deployments)
* - SQLite: FILE_PATH only
*/
enum class DatabaseConnectionRequiredField {
Expand All @@ -26,6 +28,11 @@ enum class DatabaseConnectionRequiredField {

fun ScreenStateSettings.SqlDatabaseScreenState.missingRequiredConnectionFields(): Set<DatabaseConnectionRequiredField> =
when (databaseType) {
DatabaseType.MongoDB -> buildSet {
if (host.isBlank()) add(DatabaseConnectionRequiredField.HOST)
if (port.toIntOrNull() == null) add(DatabaseConnectionRequiredField.PORT)
if (database.isBlank()) add(DatabaseConnectionRequiredField.DATABASE)
}
DatabaseType.PostgreSQL, DatabaseType.MySQL, DatabaseType.GreenPlum, DatabaseType.Hive, DatabaseType.CockroachDB, DatabaseType.ClickHouse, DatabaseType.Redshift, DatabaseType.SqlServer -> buildSet {
if (host.isBlank()) add(DatabaseConnectionRequiredField.HOST)
if (port.toIntOrNull() == null) add(DatabaseConnectionRequiredField.PORT)
Expand All @@ -49,6 +56,7 @@ fun ScreenStateSettings.SqlDatabaseScreenState.hasRequiredConnectionSettings():
fun ScreenStateSettings.SqlDatabaseScreenState.connectionPort(): Int =
when (databaseType) {
DatabaseType.PostgreSQL -> port.toIntOrNull() ?: DefaultPostgresPort
DatabaseType.MongoDB -> port.toIntOrNull() ?: DefaultMongoPort
DatabaseType.MySQL -> port.toIntOrNull() ?: DefaultMySqlPort
DatabaseType.GreenPlum -> port.toIntOrNull() ?: DefaultGreenPlumPort
DatabaseType.Hive -> port.toIntOrNull() ?: DefaultHivePort
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import org.slf4j.LoggerFactory

object LogLevel {

/**
* Sets Logback root level when Logback is the active SLF4J binding.
* If another binding or NOP is on the classpath (e.g. wrong jar order with hive-jdbc), this is a no-op.
*/
fun setLoggingLevel(level: Level) {
val root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
applyLevelOrThrow(root, level)
applyLevelIfLogback(root, level)
}

internal fun applyLevelOrThrow(rootLogger: Any, level: Level): Boolean {
Expand All @@ -19,4 +23,9 @@ object LogLevel {
logbackLogger.level = level
return true
}

private fun applyLevelIfLogback(rootLogger: Any, level: Level) {
val logbackLogger = rootLogger as? Logger ?: return
logbackLogger.level = level
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ fun DatabaseType.drawableResource(): DrawableResource = when (this) {
DatabaseType.ClickHouse -> Res.drawable.db_clickhouse_logo
DatabaseType.Redshift -> Res.drawable.db_redshift_logo
DatabaseType.SqlServer -> Res.drawable.db_sqlserver_logo
DatabaseType.MongoDB -> Res.drawable.db_mongodb_logo
}
Loading
Loading