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
15 changes: 14 additions & 1 deletion cli/flamingock-cli/CLI-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,26 @@ flamingock:
service-identifier: "flamingock-cli"
audit:
couchbase:
endpoint: "http://localhost:8000"
endpoint: "couchbase://localhost:12110"
username: "your-username"
password: "your-password"
bucket-name: "test"
table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
```

### SQL Example
```yaml
flamingock:
service-identifier: "flamingock-cli"
audit:
sql:
endpoint: "jdbc:sqlserver://localhost:1433/test-db"
username: "your-username"
password: "your-password"
sql-dialect: "SqlServer"
table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
```

### Configuration File Resolution
1. Command line argument: `--config /path/to/file.yml`
2. Default: `flamingock-cli.yml` in bin directory
Expand Down
28 changes: 26 additions & 2 deletions cli/flamingock-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ description = "Command-line interface for Flamingock audit and issue management
dependencies {
implementation(project(":core:flamingock-core"))
implementation(project(":community:flamingock-community"))
implementation(project(":utils:sql-util"))

// CLI framework
implementation("info.picocli:picocli:4.7.5")
Expand All @@ -25,6 +26,20 @@ dependencies {
implementation("software.amazon.awssdk:dynamodb:2.20.0")
implementation ("com.couchbase.client:java-client:3.7.3")

// SQL drivers
implementation("mysql:mysql-connector-java:8.0.33")
implementation("com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre8")
implementation("com.oracle.database.jdbc:ojdbc8:21.9.0.0")
implementation("org.postgresql:postgresql:42.7.3")
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.2")
implementation("com.h2database:h2:2.2.224")
implementation("org.xerial:sqlite-jdbc:3.41.2.1")
implementation("com.ibm.informix:jdbc:4.50.10")
implementation("org.firebirdsql.jdbc:jaybird:4.0.10.java8")

// HikariCP for SQL database connection pooling
implementation("com.zaxxer:HikariCP:3.4.5")

// SLF4J API - needed for interface compatibility (provided by flamingock-core)
// implementation("org.slf4j:slf4j-api:1.7.36") // Already provided by core dependencies

Expand All @@ -37,8 +52,14 @@ dependencies {
testImplementation("org.assertj:assertj-core:3.24.2")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
testImplementation("org.testcontainers:mongodb:1.19.3")
testImplementation("org.testcontainers:couchbase:1.21.3")
testImplementation("org.testcontainers:couchbase:1.19.3")
testImplementation("com.github.stefanbirkner:system-lambda:1.2.1")
// SQL Testcontainers
testImplementation("org.testcontainers:mysql:1.19.3")
testImplementation("org.testcontainers:mssqlserver:1.19.3")
testImplementation("org.testcontainers:oracle-xe:1.19.3")
testImplementation("org.testcontainers:postgresql:1.19.3")
testImplementation("org.testcontainers:mariadb:1.19.3")

}

Expand All @@ -50,6 +71,7 @@ val uberJar by tasks.registering(Jar::class) {
archiveBaseName.set("flamingock-cli")
archiveClassifier.set("uber")
archiveVersion.set(project.version.toString())
isZip64 = true

duplicatesStrategy = DuplicatesStrategy.EXCLUDE

Expand All @@ -63,7 +85,9 @@ val uberJar by tasks.registering(Jar::class) {
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
})
}) {
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
}
}

val createScripts by tasks.registering(CreateStartScripts::class) {
Expand Down
8 changes: 8 additions & 0 deletions cli/flamingock-cli/src/dist/flamingock-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ flamingock:
# password: "your-password"
# bucket-name: "test"
# table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"

# SQL Configuration (uncomment and modify to use)
# sql:
# endpoint: "jdbc:sqlserver://localhost:1433/test-db"
# username: "your-username"
# password: "your-password"
# sql-dialect: "SqlServer"
# table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ private static FlamingockConfig parseConfig(Map<String, Object> yamlData) {
databaseConfig.setCouchbase(couchbaseConfig);
}

Map<String, Object> sqlData = (Map<String, Object>) auditData.get("sql");
if (sqlData != null) {
DatabaseConfig.SqlConfig sqlConfig = new DatabaseConfig.SqlConfig();
sqlConfig.setEndpoint((String) sqlData.get("endpoint"));
sqlConfig.setUsername((String) sqlData.get("username"));
sqlConfig.setPassword((String) sqlData.get("password"));
sqlConfig.setSqlDialect((String) sqlData.get("sql-dialect"));
sqlConfig.setTable((String) sqlData.get("table"));
if (sqlData.get("properties") != null) {
sqlConfig.setProperties((Map<String, String>) sqlData.get("properties"));
}
databaseConfig.setSql(sqlConfig);
}

config.setAudit(databaseConfig);
}

Expand All @@ -118,8 +132,9 @@ public static DatabaseType detectDatabaseType(FlamingockConfig config) {
boolean hasMongoDB = config.getAudit().getMongodb() != null;
boolean hasDynamoDB = config.getAudit().getDynamodb() != null;
boolean hasCouchbase = config.getAudit().getCouchbase() != null;
boolean hasSql = config.getAudit().getSql() != null;

if (Stream.of(hasMongoDB, hasDynamoDB, hasCouchbase)
if (Stream.of(hasMongoDB, hasDynamoDB, hasCouchbase, hasSql)
.filter(b -> b)
.count()>1) {
throw new IllegalArgumentException("Multiple database configurations found. Please configure only one database type.");
Expand All @@ -131,12 +146,14 @@ public static DatabaseType detectDatabaseType(FlamingockConfig config) {
return DatabaseType.DYNAMODB;
} else if (hasCouchbase) {
return DatabaseType.COUCHBASE;
} else if (hasSql) {
return DatabaseType.SQL;
} else {
throw new IllegalArgumentException("No supported database configuration found. Please configure MongoDB, DynamoDB or Couchbase.");
throw new IllegalArgumentException("No supported database configuration found. Please configure MongoDB, DynamoDB, Couchbase or SQL.");
}
}

public enum DatabaseType {
MONGODB, DYNAMODB, COUCHBASE
MONGODB, DYNAMODB, COUCHBASE, SQL
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
*/
package io.flamingock.cli.config;

import io.flamingock.internal.common.sql.SqlDialect;

import java.util.Map;

public class DatabaseConfig {
private MongoDBConfig mongodb;
private DynamoDBConfig dynamodb;
private CouchbaseConfig couchbase;
private SqlConfig sql;

public MongoDBConfig getMongodb() {
return mongodb;
Expand All @@ -46,6 +49,14 @@ public void setCouchbase(CouchbaseConfig couchbase) {
this.couchbase = couchbase;
}

public SqlConfig getSql() {
return sql;
}

public void setSql(SqlConfig sql) {
this.sql = sql;
}

public static class MongoDBConfig {
private String connectionString;
private String database;
Expand Down Expand Up @@ -225,4 +236,90 @@ public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
}

public static class SqlConfig {
private String endpoint;
private String username;
private String password;
private SqlDialect sqlDialect;
private String table;
private Map<String, String> properties;

public String getEndpoint() {
return endpoint;
}

public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public SqlDialect getSqlDialect() {
return sqlDialect;
}

public void setSqlDialect(String sqlDialect) {
this.sqlDialect = SqlDialect.valueOf(sqlDialect.toUpperCase());
}

public String getDriverClassName() {
switch (sqlDialect) {
case MYSQL:
return "com.mysql.cj.jdbc.Driver";
case MARIADB:
return "org.mariadb.jdbc.Driver";
case POSTGRESQL:
return "org.postgresql.Driver";
case SQLITE:
return "org.sqlite.JDBC";
case H2:
return "org.h2.Driver";
case SQLSERVER:
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
case SYBASE:
return "com.sybase.jdbc4.jdbc.SybDriver";
case FIREBIRD:
return "org.firebirdsql.jdbc.FBDriver";
case INFORMIX:
return "com.informix.jdbc.IfxDriver";
case ORACLE:
return "oracle.jdbc.OracleDriver";
case DB2:
return "com.ibm.db2.jcc.DB2Driver";
default:
throw new IllegalArgumentException("Unsupported SQL Dialect: " + sqlDialect);
}
}

public String getTable() {
return table;
}

public void setTable(String table) {
this.table = table;
}

public Map<String, String> getProperties() {
return properties;
}

public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2025 Flamingock (https://www.flamingock.io)
*
* 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 io.flamingock.cli.factory;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.flamingock.cli.config.DatabaseConfig;
import io.flamingock.internal.common.sql.SqlDialect;
import org.sqlite.SQLiteDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Statement;

public class SqlDataSourceFactory {

public static DataSource createSqlDataSource(DatabaseConfig.SqlConfig config) {
if (config == null) {
throw new IllegalArgumentException("SQL configuration is required");
}

if (config.getEndpoint() == null) {
throw new IllegalArgumentException("Database endpoint is required");
}

if (config.getSqlDialect() == null) {
throw new IllegalArgumentException("Sql dialect is required");
}

if (!SqlDialect.SQLITE.equals(config.getSqlDialect())) {
if (config.getUsername() == null) {
throw new IllegalArgumentException("Database username is required");
}
if (config.getPassword() == null) {
throw new IllegalArgumentException("Database password is required");
}
}

try {
DataSource sqlDatasource;

if (config.getSqlDialect().equals(SqlDialect.SQLITE)) {
SQLiteDataSource sqliteDatasource = new SQLiteDataSource();
sqliteDatasource.setUrl(config.getEndpoint());

try (Connection conn = sqliteDatasource.getConnection();
Statement stmt = conn.createStatement()) {
stmt.execute("PRAGMA journal_mode=WAL;");
stmt.execute("PRAGMA busy_timeout=5000;");
}

sqlDatasource = sqliteDatasource;
} else {
HikariConfig datasourceConfig = new HikariConfig();
datasourceConfig.setJdbcUrl(config.getEndpoint());
datasourceConfig.setUsername(config.getUsername());
datasourceConfig.setPassword(config.getPassword());
datasourceConfig.setDriverClassName(config.getDriverClassName());

sqlDatasource = new HikariDataSource(datasourceConfig);
}

// Test the connection by listing tables
try (Connection conn = sqlDatasource.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
metaData.getTables(null, null, "%", null);
} catch (SQLException e) {
throw new RuntimeException("Failed to validate SQL DataSource connection: " + e.getMessage(), e);
}

return sqlDatasource;
} catch (Exception e) {
throw new RuntimeException("Failed to create SQL DataSource: " + e.getMessage(), e);
}
}
}
Loading
Loading