Skip to content

Commit 272bd51

Browse files
authored
Merge pull request #6 from trocco-io/feature/oauth-m2m
Implementation of oauth-m2m authentication
2 parents 91d572e + 9356781 commit 272bd51

File tree

12 files changed

+226
-15
lines changed

12 files changed

+226
-15
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@ Databricks input plugin for Embulk loads records from Databricks.
1616
- **product_version**: product version of user agent (string, default: "0.0.0")
1717
- **server_hostname**: The Databricks compute resource’s Server Hostname value, see [Compute settings for the Databricks JDBC Driver](https://docs.databricks.com/en/integrations/jdbc/compute.html). (string, required)
1818
- **http_path**: The Databricks compute resource’s HTTP Path value, see [Compute settings for the Databricks JDBC Driver](https://docs.databricks.com/en/integrations/jdbc/compute.html). (string, required)
19-
- **personal_access_token**: The Databaricks personal_access_token, see [Authentication settings for the Databricks JDBC Driver](https://docs.databricks.com/en/integrations/jdbc/authentication.html#authentication-pat). (string, required)
19+
- **auth_type**: The Databricks authentication type, personal access token (PAT)-based or machine-to-machine (M2M) authentication. (`pat`, `oauth-m2m`, default: `pat`)
20+
- If **auth_type** is `pat`,
21+
- **personal_access_token**: The Databaricks personal_access_token, see [Authentication settings for the Databricks JDBC Driver](https://docs.databricks.com/en/integrations/jdbc/authentication.html#authentication-pat). (string, required)
22+
- If **auth_type** is `m2m-auth`,
23+
- **oauth2_client_id**: The Databaricks oauth2_client_id, see [Use a service principal to authenticate with Databricks](https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html). (string, required)
24+
- **oauth2_client_secret**: The Databaricks oauth2_client_secret, see [Use a service principal to authenticate with Databricks](https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html). (string, required)
2025
- **catalog_name**: destination catalog name (string, optional)
2126
- **schema_name**: destination schema name (string, optional)
2227
- **where**: WHERE condition to filter the rows (string, default: no-condition)

build.gradle

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
id "com.palantir.git-version" version "0.13.0"
66
id "com.diffplug.spotless" version "5.15.0"
77
id "com.adarshr.test-logger" version "3.0.0"
8+
id "com.github.johnrengelman.shadow" version "6.0.0" apply false
89
}
910

1011
repositories {
@@ -32,7 +33,7 @@ dependencies {
3233
compileOnly("org.embulk:embulk-api:${embulkVersion}")
3334
compileOnly("org.embulk:embulk-spi:${embulkVersion}")
3435
compile("org.embulk:embulk-input-jdbc:0.13.2")
35-
compile('com.databricks:databricks-jdbc:2.6.34')
36+
compile project(path: ":shadow-databricks-jdbc", configuration: "shadow")
3637

3738
testImplementation "junit:junit:4.+"
3839
testImplementation "org.embulk:embulk-junit4:${embulkVersion}"
@@ -41,9 +42,10 @@ dependencies {
4142
testImplementation "org.embulk:embulk-formatter-csv:${embulkVersion}"
4243
testImplementation "org.embulk:embulk-output-file:${embulkVersion}"
4344

44-
//SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
45-
//SLF4J: Defaulting to no-operation (NOP) logger implementation
46-
//SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
45+
// Supress following logs in gradlew test.
46+
// SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
47+
// SLF4J: Defaulting to no-operation (NOP) logger implementation
48+
// SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
4749
testImplementation("org.slf4j:slf4j-simple:1.7.30")
4850
}
4951

example/test.yml.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
server_hostname:
55
http_path:
66
personal_access_token:
7+
oauth2_client_id:
8+
oauth2_client_secret:
79
catalog_name:
810
schema_name:
9-
table_prefix:
11+
table_prefix:
1012
another_catalog_name:

gradle/dependency-locks/compileClasspath.lockfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# This is a Gradle generated file for dependency locking.
22
# Manual edits can break the build and are not advised.
33
# This file is expected to be part of source control.
4-
com.databricks:databricks-jdbc:2.6.34
54
com.fasterxml.jackson.core:jackson-annotations:2.6.7
65
com.fasterxml.jackson.core:jackson-core:2.6.7
76
com.fasterxml.jackson.core:jackson-databind:2.6.7

gradle/dependency-locks/runtimeClasspath.lockfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# This is a Gradle generated file for dependency locking.
22
# Manual edits can break the build and are not advised.
33
# This file is expected to be part of source control.
4-
com.databricks:databricks-jdbc:2.6.34
54
com.fasterxml.jackson.core:jackson-annotations:2.6.7
65
com.fasterxml.jackson.core:jackson-core:2.6.7
76
com.fasterxml.jackson.core:jackson-databind:2.6.7

settings.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rootProject.name = "embulk-input-databricks"
2+
include "shadow-databricks-jdbc"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
apply plugin: "java"
2+
apply plugin: "com.github.johnrengelman.shadow"
3+
4+
repositories {
5+
mavenCentral()
6+
}
7+
8+
group = "io.trocco"
9+
version = "${rootProject.version}"
10+
description = "A helper library for embulk-input-databricks"
11+
12+
sourceCompatibility = 1.8
13+
targetCompatibility = 1.8
14+
15+
configurations {
16+
runtimeClasspath {
17+
resolutionStrategy.activateDependencyLocking()
18+
}
19+
shadow {
20+
resolutionStrategy.activateDependencyLocking()
21+
transitive = false
22+
}
23+
}
24+
25+
dependencies {
26+
compile('com.databricks:databricks-jdbc:2.6.38')
27+
}
28+
29+
shadowJar {
30+
// suppress the following undesirable log (https://stackoverflow.com/a/61475766/24393181)
31+
//
32+
// ERROR StatusLogger Unrecognized format specifier [d]
33+
// ERROR StatusLogger Unrecognized conversion specifier [d] starting at position 16 in conversion pattern.
34+
// ...
35+
exclude "**/Log4j2Plugins.dat"
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# This is a Gradle generated file for dependency locking.
2+
# Manual edits can break the build and are not advised.
3+
# This file is expected to be part of source control.
4+
com.databricks:databricks-jdbc:2.6.38
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# This is a Gradle generated file for dependency locking.
2+
# Manual edits can break the build and are not advised.
3+
# This file is expected to be part of source control.

src/main/java/org/embulk/input/DatabricksInputPlugin.java

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.sql.Connection;
44
import java.sql.DriverManager;
55
import java.sql.SQLException;
6+
import java.util.Arrays;
7+
import java.util.List;
68
import java.util.Optional;
79
import java.util.Properties;
810
import org.embulk.config.ConfigException;
@@ -30,8 +32,21 @@ public interface DatabricksPluginTask extends PluginTask {
3032
@Config("http_path")
3133
public String getHTTPPath();
3234

35+
@Config("auth_type")
36+
@ConfigDefault("\"pat\"") // oauth-m2m or pat
37+
public String getAuthType();
38+
3339
@Config("personal_access_token")
34-
public String getPersonalAccessToken();
40+
@ConfigDefault("null")
41+
public Optional<String> getPersonalAccessToken();
42+
43+
@Config("oauth2_client_id")
44+
@ConfigDefault("null")
45+
public Optional<String> getOauth2ClientId();
46+
47+
@Config("oauth2_client_secret")
48+
@ConfigDefault("null")
49+
public Optional<String> getOauth2ClientSecret();
3550

3651
@Config("catalog_name")
3752
@ConfigDefault("null")
@@ -54,6 +69,25 @@ public interface UserAgentEntry extends Task {
5469
@ConfigDefault("\"0.0.0\"")
5570
public String getProductVersion();
5671
}
72+
73+
static String fetchPersonalAccessToken(DatabricksPluginTask t) {
74+
return validatePresence(t.getPersonalAccessToken(), "personal_access_token");
75+
}
76+
77+
static String fetchOauth2ClientId(DatabricksPluginTask t) {
78+
return validatePresence(t.getOauth2ClientId(), "oauth2_client_id");
79+
}
80+
81+
static String fetchOauth2ClientSecret(DatabricksPluginTask t) {
82+
return validatePresence(t.getOauth2ClientSecret(), "oauth2_client_secret");
83+
}
84+
}
85+
86+
private static <T> T validatePresence(Optional<T> val, String varName) {
87+
if (val.isPresent()) {
88+
return val.get();
89+
}
90+
throw new ConfigException(String.format("%s must not be null.", varName));
5791
}
5892

5993
@Override
@@ -79,9 +113,22 @@ protected JdbcInputConnection newConnection(PluginTask task) throws SQLException
79113
String url = String.format("jdbc:databricks://%s:443", t.getServerHostname());
80114
Properties props = new java.util.Properties();
81115
props.put("httpPath", t.getHTTPPath());
82-
props.put("AuthMech", "3");
83-
props.put("UID", "token");
84-
props.put("PWD", t.getPersonalAccessToken());
116+
String authType = t.getAuthType();
117+
switch (authType) {
118+
case "pat":
119+
props.put("AuthMech", "3");
120+
props.put("UID", "token");
121+
props.put("PWD", DatabricksPluginTask.fetchPersonalAccessToken(t));
122+
break;
123+
case "oauth-m2m":
124+
props.put("AuthMech", "11");
125+
props.put("Auth_Flow", "1");
126+
props.put("OAuth2ClientId", DatabricksPluginTask.fetchOauth2ClientId(t));
127+
props.put("OAuth2Secret", DatabricksPluginTask.fetchOauth2ClientSecret(t));
128+
break;
129+
default:
130+
throw new ConfigException(String.format("unknown auth_type '%s'", authType));
131+
}
85132
props.put("SSL", "1");
86133
if (t.getCatalogName().isPresent()) {
87134
props.put("ConnCatalog", t.getCatalogName().get());
@@ -104,10 +151,11 @@ protected JdbcInputConnection newConnection(PluginTask task) throws SQLException
104151

105152
@Override
106153
protected void logConnectionProperties(String url, Properties props) {
154+
List<String> maskedKeys = Arrays.asList("PWD", "OAuth2Secret");
107155
Properties maskedProps = new Properties();
108156
for (Object keyObj : props.keySet()) {
109157
String key = (String) keyObj;
110-
String maskedVal = key.equals("PWD") ? "***" : props.getProperty(key);
158+
String maskedVal = maskedKeys.contains(key) ? "***" : props.getProperty(key);
111159
maskedProps.setProperty(key, maskedVal);
112160
}
113161
super.logConnectionProperties(url, maskedProps);

0 commit comments

Comments
 (0)