Skip to content

Commit dab1ae0

Browse files
authored
Added options saKeyFile and tokenFile (#77)
2 parents 7f378a2 + c794537 commit dab1ae0

File tree

10 files changed

+190
-46
lines changed

10 files changed

+190
-46
lines changed

.github/workflows/build.yaml

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,31 @@ on:
1010
type: [opened, reopened, edited, synchronize]
1111

1212
jobs:
13+
prepare:
14+
name: Prepare Maven cache
15+
runs-on: ubuntu-latest
16+
17+
env:
18+
MAVEN_ARGS: --batch-mode -Dstyle.color=always
19+
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Set up JDK
25+
uses: actions/setup-java@v4
26+
with:
27+
java-version: '8'
28+
distribution: 'temurin'
29+
cache: 'maven'
30+
31+
- name: Download dependencies for JDBC
32+
run: mvn $MAVEN_ARGS dependency:resolve-plugins dependency:go-offline
33+
1334
build:
1435
name: Build JDBC Driver
1536
runs-on: ubuntu-latest
37+
needs: prepare
1638

1739
strategy:
1840
matrix:
@@ -75,9 +97,6 @@ jobs:
7597
if: env.NEED_SDK == 'true'
7698
run: rm -rf yc
7799

78-
- name: Download dependencies
79-
run: mvn $MAVEN_ARGS dependency:resolve-plugins dependency:go-offline
80-
81100
- name: Build with Maven
82101
run: mvn $MAVEN_ARGS package
83102

@@ -144,9 +163,6 @@ jobs:
144163
if: env.NEED_SDK == 'true'
145164
run: rm -rf yc
146165

147-
- name: Download dependencies
148-
run: mvn $MAVEN_ARGS dependency:resolve-plugins dependency:go-offline
149-
150166
- name: Build with Maven
151167
run: mvn $MAVEN_ARGS test
152168

README.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
1) Drop in [JDBC driver](https://github.com/ydb-platform/ydb-jdbc-driver/releases) to classpath or pick this file in IDE
1212
2) Connect to YDB
1313
* Local or remote Docker (anonymous authentication):<br>`jdbc:ydb:grpc://localhost:2136/local`
14-
* Self-hosted cluster:<br>`jdbc:ydb:grpcs://<host>:2135/Root/testdb?secureConnectionCertificate=file:~/myca.cer`
15-
* Connect with token to the cloud instance:<br>`jdbc:ydb:grpcs://<host>:2135/path/to/database?token=file:~/my_token`
16-
* Connect with service account to the cloud instance:<br>`jdbc:ydb:grpcs://<host>:2135/path/to/database?saFile=file:~/sa_key.json`
14+
* Self-hosted cluster:<br>`jdbc:ydb:grpcs://<host>:2135/Root/testdb?secureConnectionCertificate=~/myca.cer`
15+
* Connect with token to the cloud instance:<br>`jdbc:ydb:grpcs://<host>:2135/path/to/database?tokenFile=~/my_token`
16+
* Connect with service account to the cloud instance:<br>`jdbc:ydb:grpcs://<host>:2135/path/to/database?saKeyFile=~/sa_key.json`
1717
3) Execute queries, see example in [YdbDriverExampleTest.java](jdbc/src/test/java/tech/ydb/jdbc/YdbDriverExampleTest.java)
1818

1919
### Usage with Maven
@@ -53,20 +53,15 @@ YDB JDBC Driver supports the following [authentication modes](https://ydb.tech/e
5353
### Driver properties reference
5454

5555
Driver supports the following configuration properties, which can be specified in the URL or passed via extra properties:
56-
* `saFile` - service account key for authentication, can be passed either as literal JSON value or as a file reference;
56+
* `saKeyFile` - file location of service account key for authentication;
57+
* `tokenFile` - file location with token value for token based authentication;
5758
* `iamEndpoint` - custom IAM endpoint for authentication via service account key;
58-
* `token` - token value for authentication, can be passed either as literal value or as a file reference;
5959
* `useMetadata` - boolean value, true if metadata authentication should be used, false otherwise (and default);
6060
* `metadataURL` - custom metadata endpoint;
6161
* `localDatacenter` - name of the datacenter local to the application being connected;
6262
* `secureConnection` - boolean value, true if TLS should be enforced (normally configured via `grpc://` or `grpcs://` scheme in the JDBC URL);
6363
* `secureConnectionCertificate` - custom CA certificate for TLS connections, can be passed either as literal value or as a file reference.
6464

65-
File references for `saFile`, `token` or `secureConnectionCertificate` must be prefixed with the `file:` URL scheme, for example:
66-
* `saFile=file:~/mysaley1.json`
67-
* `token=file:/opt/secret/token-file`
68-
* `secureConnectionCertificate=file:/etc/ssl/cacert.cer`
69-
7065
### Building
7166
By default all tests are run using a local YDB instance in Docker (if host has Docker or Docker Machine installed)
7267
To disable these tests run `mvn test -DYDB_DISABLE_INTEGRATION_TESTS=true`

jdbc/src/main/java/tech/ydb/jdbc/YdbConst.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public final class YdbConst {
3131
public static final String DRIVER_IS_NOT_REGISTERED = "Driver is not registered "
3232
+ "(or it has not been registered using YdbDriver.register() method)";
3333

34+
public static final String MISSING_DRIVER_OPTION = "Missing value for option ";
35+
public static final String INVALID_DRIVER_OPTION_VALUE = "Cannot process value %s for option %s: %s";
36+
3437
public static final String PREPARED_CALLS_UNSUPPORTED = "Prepared calls are not supported";
3538
public static final String ARRAYS_UNSUPPORTED = "Arrays are not supported";
3639
public static final String STRUCTS_UNSUPPORTED = "Structs are not supported";
@@ -140,8 +143,6 @@ public final class YdbConst {
140143
public static final String INDEXED_PARAMETER_PREFIX = "p";
141144
public static final String VARIABLE_PARAMETER_PREFIX = "$";
142145
public static final String AUTO_GENERATED_PARAMETER_PREFIX = VARIABLE_PARAMETER_PREFIX + "jp";
143-
public static final String DEFAULT_BATCH_PARAMETER = "$values";
144-
public static final String OPTIONAL_TYPE_SUFFIX = "?";
145146

146147
private YdbConst() {
147148
//

jdbc/src/main/java/tech/ydb/jdbc/YdbDriver.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ public int getConnectionCount() {
134134
}
135135

136136
public void close() {
137-
LOGGER.log(Level.INFO, "Closing {0} cached connection(s)...", cache.size());
138-
cache.values().forEach(YdbContext::close);
139-
cache.clear();
137+
if (!cache.isEmpty()) {
138+
LOGGER.log(Level.FINE, "Closing {0} cached connection(s)...", cache.size());
139+
cache.values().forEach(YdbContext::close);
140+
cache.clear();
141+
}
140142
}
141143

142144
public static boolean isRegistered() {

jdbc/src/main/java/tech/ydb/jdbc/context/StreamQueryResult.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,14 @@ public StreamQueryResult(String msg, YdbStatement statement, YdbQuery query, Run
8080

8181
public void onStreamResultSet(int index, ResultSetReader rsr) {
8282
CompletableFuture<Result<LazyResultSet>> future = resultFutures.get(index);
83+
8384
if (!future.isDone()) {
8485
ColumnInfo[] columns = ColumnInfo.fromResultSetReader(rsr);
85-
future.complete(Result.success(new LazyResultSet(statement, columns)));
86+
LazyResultSet rs = new LazyResultSet(statement, columns);
87+
rs.addResultSet(rsr);
88+
if (future.complete(Result.success(rs))) {
89+
return;
90+
}
8691
}
8792

8893
Result<LazyResultSet> res = future.join();

jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ public DriverPropertyInfo[] toPropertyInfo() throws SQLException {
164164
YdbConnectionProperties.USE_SECURE_CONNECTION.toInfo(properties),
165165
YdbConnectionProperties.SECURE_CONNECTION_CERTIFICATE.toInfo(properties),
166166
YdbConnectionProperties.TOKEN.toInfo(properties),
167-
YdbConnectionProperties.SERVICE_ACCOUNT_FILE.toInfo(properties),
167+
YdbConnectionProperties.TOKEN_FILE.toInfo(properties),
168+
YdbConnectionProperties.SA_KEY_FILE.toInfo(properties),
168169
YdbConnectionProperties.USE_METADATA.toInfo(properties),
169170
YdbConnectionProperties.IAM_ENDPOINT.toInfo(properties),
170171
YdbConnectionProperties.METADATA_URL.toInfo(properties),

jdbc/src/main/java/tech/ydb/jdbc/settings/YdbConnectionProperties.java

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,42 @@
22

33
import java.sql.SQLException;
44
import java.util.Properties;
5+
import java.util.logging.Level;
6+
import java.util.logging.Logger;
57

68
import tech.ydb.auth.TokenAuthProvider;
79
import tech.ydb.auth.iam.CloudAuthHelper;
810
import tech.ydb.core.auth.StaticCredentials;
911
import tech.ydb.core.grpc.BalancingSettings;
1012
import tech.ydb.core.grpc.GrpcTransportBuilder;
13+
import tech.ydb.jdbc.YdbDriver;
1114

1215

1316
public class YdbConnectionProperties {
17+
private static final Logger LOGGER = Logger.getLogger(YdbDriver.class.getName());
18+
1419
static final YdbProperty<String> TOKEN = YdbProperty.content(YdbConfig.TOKEN_KEY, "Authentication token");
1520

21+
static final YdbProperty<String> TOKEN_FILE = YdbProperty.file("tokenFile",
22+
"Path to token file for the token-based authentication");
23+
1624
static final YdbProperty<String> LOCAL_DATACENTER = YdbProperty.string("localDatacenter",
1725
"Local Datacenter");
1826

1927
static final YdbProperty<Boolean> USE_SECURE_CONNECTION = YdbProperty.bool("secureConnection",
2028
"Use TLS connection");
2129

22-
static final YdbProperty<byte[]> SECURE_CONNECTION_CERTIFICATE = YdbProperty.bytes("secureConnectionCertificate",
23-
"Use TLS connection with certificate from provided path");
30+
static final YdbProperty<byte[]> SECURE_CONNECTION_CERTIFICATE = YdbProperty.fileBytes(
31+
"secureConnectionCertificate", "Use TLS connection with certificate from provided path"
32+
);
2433

34+
@Deprecated
2535
static final YdbProperty<String> SERVICE_ACCOUNT_FILE = YdbProperty.content("saFile",
2636
"Service account file based authentication");
2737

38+
static final YdbProperty<String> SA_KEY_FILE = YdbProperty.file("saKeyFile",
39+
"Path to key file for the service account authentication");
40+
2841
static final YdbProperty<Boolean> USE_METADATA = YdbProperty.bool("useMetadata",
2942
"Use metadata service for authentication");
3043

@@ -41,7 +54,9 @@ public class YdbConnectionProperties {
4154
private final YdbValue<Boolean> useSecureConnection;
4255
private final YdbValue<byte[]> secureConnectionCertificate;
4356
private final YdbValue<String> token;
57+
private final YdbValue<String> tokenFile;
4458
private final YdbValue<String> serviceAccountFile;
59+
private final YdbValue<String> saKeyFile;
4560
private final YdbValue<Boolean> useMetadata;
4661
private final YdbValue<String> iamEndpoint;
4762
private final YdbValue<String> metadataUrl;
@@ -56,7 +71,9 @@ public YdbConnectionProperties(YdbConfig config) throws SQLException {
5671
this.useSecureConnection = USE_SECURE_CONNECTION.readValue(props);
5772
this.secureConnectionCertificate = SECURE_CONNECTION_CERTIFICATE.readValue(props);
5873
this.token = TOKEN.readValue(props);
74+
this.tokenFile = TOKEN_FILE.readValue(props);
5975
this.serviceAccountFile = SERVICE_ACCOUNT_FILE.readValue(props);
76+
this.saKeyFile = SA_KEY_FILE.readValue(props);
6077
this.useMetadata = USE_METADATA.readValue(props);
6178
this.iamEndpoint = IAM_ENDPOINT.readValue(props);
6279
this.metadataUrl = METADATA_URL.readValue(props);
@@ -87,33 +104,78 @@ public GrpcTransportBuilder applyToGrpcTransport(GrpcTransportBuilder builder) {
87104
builder = builder.withSecureConnection(secureConnectionCertificate.getValue());
88105
}
89106

107+
String usedProvider = null;
108+
109+
if (username != null && !username.isEmpty()) {
110+
builder = builder.withAuthProvider(new StaticCredentials(username, password));
111+
usedProvider = "username & password credentials";
112+
}
113+
114+
if (useMetadata.hasValue()) {
115+
if (usedProvider != null) {
116+
LOGGER.log(Level.WARNING, "Dublicate authentication config! Metadata credentials replaces {0}",
117+
usedProvider);
118+
}
119+
120+
if (metadataUrl.hasValue()) {
121+
String url = metadataUrl.getValue();
122+
builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider(url));
123+
} else {
124+
builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider());
125+
}
126+
usedProvider = "metadata credentials";
127+
}
128+
129+
if (tokenFile.hasValue()) {
130+
if (usedProvider != null) {
131+
LOGGER.log(Level.WARNING, "Dublicate authentication config! Token credentials replaces {0}",
132+
usedProvider);
133+
}
134+
builder = builder.withAuthProvider(new TokenAuthProvider(tokenFile.getValue()));
135+
usedProvider = "token file credentitals";
136+
}
137+
90138
if (token.hasValue()) {
139+
if (usedProvider != null) {
140+
LOGGER.log(Level.WARNING, "Dublicate authentication config! Token credentials replaces {0}",
141+
usedProvider);
142+
}
91143
builder = builder.withAuthProvider(new TokenAuthProvider(token.getValue()));
144+
usedProvider = "token value credentitals";
92145
}
93146

94-
if (serviceAccountFile.hasValue()) {
95-
String json = serviceAccountFile.getValue();
147+
if (saKeyFile.hasValue()) {
148+
if (usedProvider != null) {
149+
LOGGER.log(Level.WARNING, "Dublicate authentication config! Token credentials replaces {0}",
150+
usedProvider);
151+
}
152+
String json = saKeyFile.getValue();
96153
if (iamEndpoint.hasValue()) {
97154
String endpoint = iamEndpoint.getValue();
98155
builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json, endpoint));
99156
} else {
100157
builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json));
101158
}
159+
builder = builder.withAuthProvider(new TokenAuthProvider(token.getValue()));
160+
usedProvider = "service account credentitals";
102161
}
103162

104-
if (useMetadata.hasValue()) {
105-
if (metadataUrl.hasValue()) {
106-
String url = metadataUrl.getValue();
107-
builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider(url));
163+
if (serviceAccountFile.hasValue()) {
164+
LOGGER.warning("Option 'saFile' is deprecated and will be removed in next versions. "
165+
+ "Use options 'saKeyFile' instead");
166+
if (usedProvider != null) {
167+
LOGGER.log(Level.WARNING, "Dublicate authentication config! Token credentials replaces {0}",
168+
usedProvider);
169+
}
170+
String json = serviceAccountFile.getValue();
171+
if (iamEndpoint.hasValue()) {
172+
String endpoint = iamEndpoint.getValue();
173+
builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json, endpoint));
108174
} else {
109-
builder = builder.withAuthProvider(CloudAuthHelper.getMetadataAuthProvider());
175+
builder = builder.withAuthProvider(CloudAuthHelper.getServiceAccountJsonAuthProvider(json));
110176
}
111177
}
112178

113-
if (username != null && !username.isEmpty()) {
114-
builder = builder.withAuthProvider(new StaticCredentials(username, password));
115-
}
116-
117179
return builder;
118180
}
119181
}

jdbc/src/main/java/tech/ydb/jdbc/settings/YdbLookup.java

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,78 @@
11
package tech.ydb.jdbc.settings;
22

3+
import java.io.FileInputStream;
34
import java.io.IOException;
45
import java.io.InputStream;
56
import java.io.InputStreamReader;
67
import java.io.Reader;
78
import java.net.MalformedURLException;
89
import java.net.URL;
10+
import java.sql.SQLException;
911
import java.util.Optional;
1012

1113
import com.google.common.io.ByteStreams;
1214
import com.google.common.io.CharStreams;
1315

16+
import tech.ydb.jdbc.YdbConst;
17+
1418
/**
1519
*
1620
* @author Aleksandr Gorshenin
1721
*/
1822
public class YdbLookup {
1923
private static final String FILE_REF = "file:";
2024
private static final String CLASSPATH_REF = "classpath:";
21-
private static final String HOME_REF = "~";
25+
private static final String HOME_REF = "~/";
2226
private static final String FILE_HOME_REF = FILE_REF + HOME_REF;
2327

2428
private YdbLookup() { }
2529

30+
public static String readFileAsString(String name, String value) throws SQLException {
31+
try (Reader reader = new InputStreamReader(readFile(name, value))) {
32+
return CharStreams.toString(reader).trim();
33+
} catch (IOException e) {
34+
String msg = String.format(YdbConst.INVALID_DRIVER_OPTION_VALUE, value, name, e.getMessage());
35+
throw new SQLException(msg);
36+
}
37+
}
38+
39+
public static byte[] readFileAsBytes(String name, String value) throws SQLException {
40+
try (InputStream is = readFile(name, value)) {
41+
return ByteStreams.toByteArray(is);
42+
} catch (IOException e) {
43+
String msg = String.format(YdbConst.INVALID_DRIVER_OPTION_VALUE, value, name, e.getMessage());
44+
throw new SQLException(msg);
45+
}
46+
}
47+
48+
private static InputStream readFile(String name, String value) throws SQLException, IOException {
49+
if (value == null || value.trim().isEmpty()) {
50+
throw new SQLException(YdbConst.MISSING_DRIVER_OPTION + name);
51+
}
52+
53+
String path = value.trim();
54+
if (path.toLowerCase().startsWith(CLASSPATH_REF)) {
55+
URL resource = ClassLoader.getSystemResource(path.substring(CLASSPATH_REF.length()));
56+
if (resource == null) {
57+
String msg = String.format(YdbConst.INVALID_DRIVER_OPTION_VALUE, value, name, "resource not found");
58+
throw new SQLException(msg);
59+
}
60+
return resource.openStream();
61+
}
62+
63+
if (path.toLowerCase().startsWith(FILE_REF)) {
64+
path = path.substring(FILE_REF.length());
65+
}
66+
67+
if (path.startsWith(HOME_REF)) {
68+
String home = System.getProperty("user.home");
69+
path = path.substring(HOME_REF.length() - 1) + home;
70+
}
71+
72+
return new FileInputStream(path);
73+
}
74+
75+
2676
public static String stringFileReference(String ref) {
2777
Optional<URL> urlOpt = resolvePath(ref);
2878
if (urlOpt.isPresent()) {
@@ -56,8 +106,8 @@ static Optional<URL> resolvePath(String ref) {
56106
try {
57107
String home = System.getProperty("user.home");
58108
String fixedRef = ref.startsWith(HOME_REF)
59-
? ref.substring(HOME_REF.length())
60-
: ref.substring(FILE_HOME_REF.length());
109+
? ref.substring(HOME_REF.length() - 1)
110+
: ref.substring(FILE_HOME_REF.length() - 1);
61111
return Optional.of(new URL(FILE_REF + home + fixedRef));
62112
} catch (MalformedURLException e) {
63113
throw new RuntimeException("Unable to parse ref from home: " + ref, e);

0 commit comments

Comments
 (0)