Skip to content

Commit 696a3d7

Browse files
bercianordieppa
authored andcommitted
feat(cli): support for SQL audit store(#740)
1 parent 972c11f commit 696a3d7

File tree

16 files changed

+562
-9
lines changed

16 files changed

+562
-9
lines changed

cli/flamingock-cli/CLI-README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,26 @@ flamingock:
121121
service-identifier: "flamingock-cli"
122122
audit:
123123
couchbase:
124-
endpoint: "http://localhost:8000"
124+
endpoint: "couchbase://localhost:12110"
125125
username: "your-username"
126126
password: "your-password"
127127
bucket-name: "test"
128128
table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
129129
```
130130
131+
### SQL Example
132+
```yaml
133+
flamingock:
134+
service-identifier: "flamingock-cli"
135+
audit:
136+
sql:
137+
endpoint: "jdbc:sqlserver://localhost:1433/test-db"
138+
username: "your-username"
139+
password: "your-password"
140+
sql-dialect: "SqlServer"
141+
table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
142+
```
143+
131144
### Configuration File Resolution
132145
1. Command line argument: `--config /path/to/file.yml`
133146
2. Default: `flamingock-cli.yml` in bin directory

cli/flamingock-cli/build.gradle.kts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ description = "Command-line interface for Flamingock audit and issue management
1010
dependencies {
1111
implementation(project(":core:flamingock-core"))
1212
implementation(project(":community:flamingock-community"))
13+
implementation(project(":utils:sql-util"))
1314

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

29+
// SQL drivers
30+
implementation("mysql:mysql-connector-java:8.0.33")
31+
implementation("com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre8")
32+
implementation("com.oracle.database.jdbc:ojdbc8:21.9.0.0")
33+
implementation("org.postgresql:postgresql:42.7.3")
34+
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.2")
35+
implementation("com.h2database:h2:2.2.224")
36+
implementation("org.xerial:sqlite-jdbc:3.41.2.1")
37+
implementation("com.ibm.informix:jdbc:4.50.10")
38+
implementation("org.firebirdsql.jdbc:jaybird:4.0.10.java8")
39+
40+
// HikariCP for SQL database connection pooling
41+
implementation("com.zaxxer:HikariCP:3.4.5")
42+
2843
// SLF4J API - needed for interface compatibility (provided by flamingock-core)
2944
// implementation("org.slf4j:slf4j-api:1.7.36") // Already provided by core dependencies
3045

@@ -37,8 +52,14 @@ dependencies {
3752
testImplementation("org.assertj:assertj-core:3.24.2")
3853
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
3954
testImplementation("org.testcontainers:mongodb:1.19.3")
40-
testImplementation("org.testcontainers:couchbase:1.21.3")
55+
testImplementation("org.testcontainers:couchbase:1.19.3")
4156
testImplementation("com.github.stefanbirkner:system-lambda:1.2.1")
57+
// SQL Testcontainers
58+
testImplementation("org.testcontainers:mysql:1.19.3")
59+
testImplementation("org.testcontainers:mssqlserver:1.19.3")
60+
testImplementation("org.testcontainers:oracle-xe:1.19.3")
61+
testImplementation("org.testcontainers:postgresql:1.19.3")
62+
testImplementation("org.testcontainers:mariadb:1.19.3")
4263

4364
}
4465

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

5476
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
5577

@@ -63,7 +85,9 @@ val uberJar by tasks.registering(Jar::class) {
6385
dependsOn(configurations.runtimeClasspath)
6486
from({
6587
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
66-
})
88+
}) {
89+
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA")
90+
}
6791
}
6892

6993
val createScripts by tasks.registering(CreateStartScripts::class) {

cli/flamingock-cli/src/dist/flamingock-cli.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ flamingock:
2626
# password: "your-password"
2727
# bucket-name: "test"
2828
# table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"
29+
30+
# SQL Configuration (uncomment and modify to use)
31+
# sql:
32+
# endpoint: "jdbc:sqlserver://localhost:1433/test-db"
33+
# username: "your-username"
34+
# password: "your-password"
35+
# sql-dialect: "SqlServer"
36+
# table: "flamingockAuditLog" # Optional, defaults to "flamingockAuditLog"

cli/flamingock-cli/src/main/java/io/flamingock/cli/config/ConfigLoader.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ private static FlamingockConfig parseConfig(Map<String, Object> yamlData) {
104104
databaseConfig.setCouchbase(couchbaseConfig);
105105
}
106106

107+
Map<String, Object> sqlData = (Map<String, Object>) auditData.get("sql");
108+
if (sqlData != null) {
109+
DatabaseConfig.SqlConfig sqlConfig = new DatabaseConfig.SqlConfig();
110+
sqlConfig.setEndpoint((String) sqlData.get("endpoint"));
111+
sqlConfig.setUsername((String) sqlData.get("username"));
112+
sqlConfig.setPassword((String) sqlData.get("password"));
113+
sqlConfig.setSqlDialect((String) sqlData.get("sql-dialect"));
114+
sqlConfig.setTable((String) sqlData.get("table"));
115+
if (sqlData.get("properties") != null) {
116+
sqlConfig.setProperties((Map<String, String>) sqlData.get("properties"));
117+
}
118+
databaseConfig.setSql(sqlConfig);
119+
}
120+
107121
config.setAudit(databaseConfig);
108122
}
109123

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

122-
if (Stream.of(hasMongoDB, hasDynamoDB, hasCouchbase)
137+
if (Stream.of(hasMongoDB, hasDynamoDB, hasCouchbase, hasSql)
123138
.filter(b -> b)
124139
.count()>1) {
125140
throw new IllegalArgumentException("Multiple database configurations found. Please configure only one database type.");
@@ -131,12 +146,14 @@ public static DatabaseType detectDatabaseType(FlamingockConfig config) {
131146
return DatabaseType.DYNAMODB;
132147
} else if (hasCouchbase) {
133148
return DatabaseType.COUCHBASE;
149+
} else if (hasSql) {
150+
return DatabaseType.SQL;
134151
} else {
135-
throw new IllegalArgumentException("No supported database configuration found. Please configure MongoDB, DynamoDB or Couchbase.");
152+
throw new IllegalArgumentException("No supported database configuration found. Please configure MongoDB, DynamoDB, Couchbase or SQL.");
136153
}
137154
}
138155

139156
public enum DatabaseType {
140-
MONGODB, DYNAMODB, COUCHBASE
157+
MONGODB, DYNAMODB, COUCHBASE, SQL
141158
}
142159
}

cli/flamingock-cli/src/main/java/io/flamingock/cli/config/DatabaseConfig.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
*/
1616
package io.flamingock.cli.config;
1717

18+
import io.flamingock.internal.common.sql.SqlDialect;
19+
1820
import java.util.Map;
1921

2022
public class DatabaseConfig {
2123
private MongoDBConfig mongodb;
2224
private DynamoDBConfig dynamodb;
2325
private CouchbaseConfig couchbase;
26+
private SqlConfig sql;
2427

2528
public MongoDBConfig getMongodb() {
2629
return mongodb;
@@ -46,6 +49,14 @@ public void setCouchbase(CouchbaseConfig couchbase) {
4649
this.couchbase = couchbase;
4750
}
4851

52+
public SqlConfig getSql() {
53+
return sql;
54+
}
55+
56+
public void setSql(SqlConfig sql) {
57+
this.sql = sql;
58+
}
59+
4960
public static class MongoDBConfig {
5061
private String connectionString;
5162
private String database;
@@ -225,4 +236,90 @@ public void setProperties(Map<String, String> properties) {
225236
this.properties = properties;
226237
}
227238
}
239+
240+
public static class SqlConfig {
241+
private String endpoint;
242+
private String username;
243+
private String password;
244+
private SqlDialect sqlDialect;
245+
private String table;
246+
private Map<String, String> properties;
247+
248+
public String getEndpoint() {
249+
return endpoint;
250+
}
251+
252+
public void setEndpoint(String endpoint) {
253+
this.endpoint = endpoint;
254+
}
255+
256+
public String getUsername() {
257+
return username;
258+
}
259+
260+
public void setUsername(String username) {
261+
this.username = username;
262+
}
263+
264+
public String getPassword() {
265+
return password;
266+
}
267+
268+
public void setPassword(String password) {
269+
this.password = password;
270+
}
271+
272+
public SqlDialect getSqlDialect() {
273+
return sqlDialect;
274+
}
275+
276+
public void setSqlDialect(String sqlDialect) {
277+
this.sqlDialect = SqlDialect.valueOf(sqlDialect.toUpperCase());
278+
}
279+
280+
public String getDriverClassName() {
281+
switch (sqlDialect) {
282+
case MYSQL:
283+
return "com.mysql.cj.jdbc.Driver";
284+
case MARIADB:
285+
return "org.mariadb.jdbc.Driver";
286+
case POSTGRESQL:
287+
return "org.postgresql.Driver";
288+
case SQLITE:
289+
return "org.sqlite.JDBC";
290+
case H2:
291+
return "org.h2.Driver";
292+
case SQLSERVER:
293+
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
294+
case SYBASE:
295+
return "com.sybase.jdbc4.jdbc.SybDriver";
296+
case FIREBIRD:
297+
return "org.firebirdsql.jdbc.FBDriver";
298+
case INFORMIX:
299+
return "com.informix.jdbc.IfxDriver";
300+
case ORACLE:
301+
return "oracle.jdbc.OracleDriver";
302+
case DB2:
303+
return "com.ibm.db2.jcc.DB2Driver";
304+
default:
305+
throw new IllegalArgumentException("Unsupported SQL Dialect: " + sqlDialect);
306+
}
307+
}
308+
309+
public String getTable() {
310+
return table;
311+
}
312+
313+
public void setTable(String table) {
314+
this.table = table;
315+
}
316+
317+
public Map<String, String> getProperties() {
318+
return properties;
319+
}
320+
321+
public void setProperties(Map<String, String> properties) {
322+
this.properties = properties;
323+
}
324+
}
228325
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.cli.factory;
17+
18+
import com.zaxxer.hikari.HikariConfig;
19+
import com.zaxxer.hikari.HikariDataSource;
20+
import io.flamingock.cli.config.DatabaseConfig;
21+
import io.flamingock.internal.common.sql.SqlDialect;
22+
import org.sqlite.SQLiteDataSource;
23+
24+
import javax.sql.DataSource;
25+
import java.sql.Connection;
26+
import java.sql.DatabaseMetaData;
27+
import java.sql.SQLException;
28+
import java.sql.Statement;
29+
30+
public class SqlDataSourceFactory {
31+
32+
public static DataSource createSqlDataSource(DatabaseConfig.SqlConfig config) {
33+
if (config == null) {
34+
throw new IllegalArgumentException("SQL configuration is required");
35+
}
36+
37+
if (config.getEndpoint() == null) {
38+
throw new IllegalArgumentException("Database endpoint is required");
39+
}
40+
41+
if (config.getSqlDialect() == null) {
42+
throw new IllegalArgumentException("Sql dialect is required");
43+
}
44+
45+
if (!SqlDialect.SQLITE.equals(config.getSqlDialect())) {
46+
if (config.getUsername() == null) {
47+
throw new IllegalArgumentException("Database username is required");
48+
}
49+
if (config.getPassword() == null) {
50+
throw new IllegalArgumentException("Database password is required");
51+
}
52+
}
53+
54+
try {
55+
DataSource sqlDatasource;
56+
57+
if (config.getSqlDialect().equals(SqlDialect.SQLITE)) {
58+
SQLiteDataSource sqliteDatasource = new SQLiteDataSource();
59+
sqliteDatasource.setUrl(config.getEndpoint());
60+
61+
try (Connection conn = sqliteDatasource.getConnection();
62+
Statement stmt = conn.createStatement()) {
63+
stmt.execute("PRAGMA journal_mode=WAL;");
64+
stmt.execute("PRAGMA busy_timeout=5000;");
65+
}
66+
67+
sqlDatasource = sqliteDatasource;
68+
} else {
69+
HikariConfig datasourceConfig = new HikariConfig();
70+
datasourceConfig.setJdbcUrl(config.getEndpoint());
71+
datasourceConfig.setUsername(config.getUsername());
72+
datasourceConfig.setPassword(config.getPassword());
73+
datasourceConfig.setDriverClassName(config.getDriverClassName());
74+
75+
sqlDatasource = new HikariDataSource(datasourceConfig);
76+
}
77+
78+
// Test the connection by listing tables
79+
try (Connection conn = sqlDatasource.getConnection()) {
80+
DatabaseMetaData metaData = conn.getMetaData();
81+
metaData.getTables(null, null, "%", null);
82+
} catch (SQLException e) {
83+
throw new RuntimeException("Failed to validate SQL DataSource connection: " + e.getMessage(), e);
84+
}
85+
86+
return sqlDatasource;
87+
} catch (Exception e) {
88+
throw new RuntimeException("Failed to create SQL DataSource: " + e.getMessage(), e);
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)