Skip to content

Commit 24d68e5

Browse files
committed
feat(build): extract authmgr-oauth2-agent module with no Iceberg dependency
Fixes #239. Introduces a new `:authmgr-oauth2-agent` Gradle module containing the entire OAuth2 token engine (`OAuth2Agent`, all `Flow` implementations, `OAuth2Config`, and every supporting subpackage) with zero Iceberg compile-time dependencies, making it usable by non-Iceberg callers (custom REST clients, Kafka connectors, ad-hoc tooling). `:authmgr-oauth2-core` is now a thin two-class Iceberg SPI adapter (`OAuth2Manager` + `OAuth2Session`). Bundle modules (`runtime`, `standalone`) continue to shadow `:authmgr-oauth2-core` and pick up the agent transitively; their sources/javadoc JARs now merge both modules. Test fixtures are split accordingly: agent `testFixtures` contain the Iceberg-free `TestEnvironment` and all expectation helpers; core `testFixtures` add `IcebergTestEnvironment extends TestEnvironment` and `IcebergRestExpectation` for the two Iceberg-specific catalog mocks. The new `:authmgr-oauth2-agent` module now has a new root package: `com.dremio.iceberg.authmgr.oauth2.agent`. The existing `:authmgr-oauth2-core` keeps its current package: `com.dremio.iceberg.authmgr.oauth2`, in order to avoid user-facing regressions since this module contains the Auth Manager implementation.
1 parent 2d797e8 commit 24d68e5

149 files changed

Lines changed: 1233 additions & 1056 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build-logic/src/main/kotlin/authmgr-bundle.gradle.kts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ plugins {
3434
id("authmgr-java-production")
3535
}
3636

37-
// Create configurations to hold the core project's source and javadoc artifacts
38-
// These will be used to copy the core project's source and javadoc jars into this project's
39-
// artifacts
37+
// Create configurations to hold the core and agent projects' source and javadoc artifacts.
38+
// These will be used to copy those projects' source and javadoc jars into this project's artifacts.
4039
val coreSources by
4140
configurations.creating {
4241
isCanBeConsumed = false
@@ -59,6 +58,28 @@ val coreJavadoc by
5958
}
6059
}
6160

61+
val agentSources by
62+
configurations.creating {
63+
isCanBeConsumed = false
64+
isCanBeResolved = true
65+
attributes {
66+
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
67+
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
68+
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
69+
}
70+
}
71+
72+
val agentJavadoc by
73+
configurations.creating {
74+
isCanBeConsumed = false
75+
isCanBeResolved = true
76+
attributes {
77+
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
78+
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
79+
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.JAVADOC))
80+
}
81+
}
82+
6283
dependencies {
6384
api(project(":authmgr-oauth2-core")) {
6485
// exclude dependencies that are already provided by Iceberg
@@ -69,6 +90,8 @@ dependencies {
6990
}
7091
coreSources(project(":authmgr-oauth2-core", "sourcesElements"))
7192
coreJavadoc(project(":authmgr-oauth2-core", "javadocElements"))
93+
agentSources(project(":authmgr-oauth2-agent", "sourcesElements"))
94+
agentJavadoc(project(":authmgr-oauth2-agent", "javadocElements"))
7295
}
7396

7497
val shadowJar = tasks.named<ShadowJar>("shadowJar")
@@ -114,18 +137,20 @@ shadowJar.configure {
114137

115138
tasks.named("assemble").configure { dependsOn("shadowJar") }
116139

117-
// Configure the source jar to copy from the core project's source jar
140+
// Configure the source jar to copy from both the core and agent projects' source jars
118141
tasks.named<Jar>("sourcesJar") {
119-
dependsOn(":authmgr-oauth2-core:sourcesJar")
142+
dependsOn(":authmgr-oauth2-core:sourcesJar", ":authmgr-oauth2-agent:sourcesJar")
120143
duplicatesStrategy = DuplicatesStrategy.INCLUDE // LICENSE files may be duplicated
121144
from({ coreSources.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
145+
from({ agentSources.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
122146
}
123147

124-
// Configure the javadoc jar to copy from the core project's javadoc jar
148+
// Configure the javadoc jar to copy from both the core and agent projects' javadoc jars
125149
tasks.named<Jar>("javadocJar") {
126-
dependsOn(":authmgr-oauth2-core:javadocJar")
150+
dependsOn(":authmgr-oauth2-core:javadocJar", ":authmgr-oauth2-agent:javadocJar")
127151
duplicatesStrategy = DuplicatesStrategy.INCLUDE // LICENSE files may be duplicated
128152
from({ coreJavadoc.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
153+
from({ agentJavadoc.incoming.artifactView { lenient(true) }.files.map { zipTree(it) } })
129154
}
130155

131156
// Skip the javadoc generation task as we'll copy from the core project

build-logic/src/main/kotlin/authmgr-root.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ plugins {
2626
spotless {
2727
java {
2828
target("**/*.java")
29-
googleJavaFormat()
29+
googleJavaFormat("1.25.2")
3030
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"))
3131
endWithNewline()
3232
}

gradle/projects.main.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
authmgr-bom=bom
2222
authmgr-immutables=tools/immutables
23+
authmgr-oauth2-agent=oauth2/agent
2324
authmgr-oauth2-core=oauth2/core
2425

2526
# Bundles

oauth2/agent/build.gradle.kts

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (C) 2025 Dremio Corporation
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+
17+
import kotlin.time.Duration
18+
import kotlin.time.Duration.Companion.seconds
19+
20+
plugins {
21+
id("authmgr-java-production")
22+
id("authmgr-java-testing")
23+
id("authmgr-maven")
24+
}
25+
26+
description = "OAuth2 Agent for Dremio AuthManager"
27+
28+
ext { set("mavenName", "Auth Manager for Apache Iceberg - OAuth2 - Agent") }
29+
30+
val docs by
31+
configurations.creating {
32+
description = "Dependencies for generating configuration documentation"
33+
isCanBeResolved = true
34+
isCanBeConsumed = false
35+
isVisible = false
36+
}
37+
38+
dependencies {
39+
api(libs.nimbus.oauth2.oidc.sdk) {
40+
exclude(group = "com.github.stephenc.jcip", module = "jcip-annotations")
41+
}
42+
api(libs.nimbus.jose.jwt)
43+
44+
implementation(libs.httpclient5)
45+
46+
implementation(libs.smallrye.config)
47+
48+
// optional, but recommended for advanced cryptography (private_key_jwt client auth, DPoP)
49+
compileOnly(libs.bouncycastle.bcpkix)
50+
51+
implementation(libs.slf4j.api)
52+
implementation(libs.caffeine)
53+
54+
implementation(libs.jakarta.annotation.api)
55+
compileOnly(libs.errorprone.annotations)
56+
57+
compileOnly(project(":authmgr-immutables"))
58+
annotationProcessor(project(":authmgr-immutables", configuration = "processor"))
59+
60+
testFixturesApi(project(":authmgr-oauth2-tests"))
61+
62+
testFixturesApi(platform(libs.junit.bom))
63+
testFixturesApi("org.junit.jupiter:junit-jupiter")
64+
testFixturesApi(libs.junit.pioneer)
65+
66+
testFixturesApi(libs.assertj.core)
67+
testFixturesApi(libs.mockito.core)
68+
69+
testFixturesApi(libs.nimbus.oauth2.oidc.sdk)
70+
testFixturesApi(libs.nimbus.jose.jwt)
71+
72+
testFixturesApi(libs.guava)
73+
74+
testFixturesApi(platform(libs.testcontainers.bom))
75+
testFixturesApi("org.testcontainers:testcontainers")
76+
testFixturesApi("org.testcontainers:testcontainers-junit-jupiter")
77+
testFixturesApi(libs.keycloak.admin.client)
78+
testFixturesApi(libs.testcontainers.keycloak)
79+
80+
testFixturesImplementation(libs.bouncycastle.bcpkix)
81+
82+
// Required to compile expectation classes
83+
testFixturesCompileOnly(libs.mockserver.netty)
84+
testFixturesCompileOnly(libs.mockserver.client.java)
85+
86+
testFixturesCompileOnly(project(":authmgr-immutables"))
87+
testFixturesAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))
88+
89+
testImplementation(libs.mockserver.netty)
90+
testImplementation(libs.mockserver.client.java)
91+
92+
testImplementation(libs.bouncycastle.bcpkix)
93+
94+
testCompileOnly(project(":authmgr-immutables"))
95+
testAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))
96+
97+
intTestCompileOnly(project(":authmgr-immutables"))
98+
intTestAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))
99+
100+
intTestRuntimeOnly(libs.bouncycastle.bcpkix)
101+
102+
longTestCompileOnly(project(":authmgr-immutables"))
103+
longTestAnnotationProcessor(project(":authmgr-immutables", configuration = "processor"))
104+
105+
longTestRuntimeOnly(libs.bouncycastle.bcpkix)
106+
107+
docs("com.thoughtworks.qdox:qdox:2.2.0")
108+
}
109+
110+
tasks.named<Test>("test").configure {
111+
configForks(4)
112+
commonTestConfig()
113+
}
114+
115+
tasks.named<Test>("intTest").configure {
116+
configForks(3)
117+
commonTestConfig()
118+
}
119+
120+
val bouncyCastle = configurations.create("bouncyCastle")
121+
122+
dependencies { bouncyCastle(libs.bouncycastle.bcpkix) }
123+
124+
tasks.register<Test>("intTestNoBouncyCastle") {
125+
description = "Runs integration tests without BouncyCastle dependencies"
126+
group = "verification"
127+
configForks(3)
128+
commonTestConfig()
129+
shouldRunAfter("test")
130+
useJUnitPlatform()
131+
testClassesDirs = sourceSets.intTest.get().output.classesDirs
132+
classpath = sourceSets.intTest.get().runtimeClasspath - bouncyCastle
133+
}
134+
135+
tasks.named("check") { dependsOn("intTestNoBouncyCastle") }
136+
137+
tasks.named<Test>("longTest").configure {
138+
configForks(3)
139+
commonTestConfig()
140+
if (System.getProperty("authmgr.it.long.total") != null) {
141+
val total = Duration.parse(System.getProperty("authmgr.it.long.total"))
142+
systemProperty("authmgr.it.long.total", total.toIsoString())
143+
// Add a 10-second safety window to the tests default timeout
144+
systemProperty(
145+
"junit.jupiter.execution.timeout.testable.method.default",
146+
(total + 10.seconds).inWholeSeconds.toString() + " s",
147+
)
148+
}
149+
}
150+
151+
val mockitoAgent = configurations.create("mockitoAgent")
152+
153+
dependencies { mockitoAgent(libs.mockito.core) { isTransitive = false } }
154+
155+
tasks { test { jvmArgs("-javaagent:${mockitoAgent.asPath}") } }
156+
157+
fun Test.configForks(forks: Int) {
158+
if (System.getenv("CI") == null) {
159+
maxParallelForks = forks
160+
}
161+
}
162+
163+
fun Test.commonTestConfig() {
164+
val outputMemoryUsage = System.getProperty("authmgr.test.mockserver.outputMemoryUsage")
165+
if (outputMemoryUsage.toBoolean()) {
166+
val outputDir =
167+
project.layout.buildDirectory.dir("reports/mockserver/${this.name}").get().asFile.absolutePath
168+
outputs.dir(outputDir)
169+
File(outputDir).mkdirs()
170+
systemProperty("authmgr.test.mockserver.memoryUsageCsvDirectory", outputDir)
171+
}
172+
}
173+
174+
sourceSets.create("docs") {
175+
java.srcDir("src/docs/java")
176+
resources.srcDir("src/docs/resources")
177+
compileClasspath += docs
178+
runtimeClasspath += docs
179+
}
180+
181+
tasks.named("processDocsResources", ProcessResources::class) {
182+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
183+
}
184+
185+
tasks.register<JavaExec>("generateDocs") {
186+
group = "documentation"
187+
description = "Generates configuration documentation from OAuth2AgentConfig"
188+
mainClass.set("com.dremio.iceberg.authmgr.oauth2.agent.docs.DocumentationGenerator")
189+
classpath = sourceSets.getByName("docs").runtimeClasspath
190+
191+
val inputFile =
192+
project.file("src/main/java/com/dremio/iceberg/authmgr/oauth2/agent/OAuth2AgentConfig.java")
193+
val outputFile = rootProject.file("docs/configuration.md")
194+
195+
val headerFile = sourceSets.getByName("docs").resources.singleFile
196+
val header = headerFile.readText()
197+
198+
inputs.files(inputFile, headerFile)
199+
outputs.file(outputFile)
200+
201+
args(inputFile.absolutePath, header, outputFile.absolutePath)
202+
203+
doFirst { outputFile.parentFile.mkdirs() }
204+
}
205+
206+
tasks.named("publish") { dependsOn("generateDocs") }
207+
208+
rootProject.tasks.named("spotlessMarkdown") { dependsOn(":authmgr-oauth2-agent:generateDocs") }
209+
210+
rootProject.tasks.named("rat") { dependsOn(":authmgr-oauth2-agent:generateDocs") }

oauth2/core/src/docs/java/com/dremio/iceberg/authmgr/oauth2/docs/DocumentationGenerator.java renamed to oauth2/agent/src/docs/java/com/dremio/iceberg/authmgr/oauth2/agent/docs/DocumentationGenerator.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.dremio.iceberg.authmgr.oauth2.docs;
16+
package com.dremio.iceberg.authmgr.oauth2.agent.docs;
1717

1818
import com.thoughtworks.qdox.JavaProjectBuilder;
1919
import com.thoughtworks.qdox.model.JavaClass;
@@ -36,7 +36,7 @@
3636
/**
3737
* Parses the properties from the source code and generates documentation for them.
3838
*
39-
* <p>This generator is mostly intended to parse the `OAuth2Properties` class. The parser relies
39+
* <p>This generator is mostly intended to parse the `OAuth2AgentConfig` class. The parser relies
4040
* heavily on conventions, such as the use of `PREFIX` fields, or fields starting with `DEFAULT_`,
4141
* or the presence of nested classes to structure the properties into sections.
4242
*/
@@ -76,7 +76,8 @@ public class DocumentationGenerator {
7676
KNOWN_REFS = Map.copyOf(refs);
7777
}
7878

79-
private static final String ROOT_CLASS_NAME = "com.dremio.iceberg.authmgr.oauth2.OAuth2Config";
79+
private static final String ROOT_CLASS_NAME =
80+
"com.dremio.iceberg.authmgr.oauth2.agent.OAuth2AgentConfig";
8081

8182
private final Path rootConfigFile;
8283
private final String header;
@@ -218,7 +219,7 @@ private String sanitizeDescription(Section section, String text) {
218219
private String resolveReference(Section section, String ref, String text) {
219220
String refTarget = KNOWN_REFS.get(ref);
220221
if (refTarget == null) {
221-
if (ref.equals("OAuth2Config#PREFIX")) {
222+
if (ref.equals("OAuth2AgentConfig#PREFIX")) {
222223
refTarget = rootPrefix;
223224
} else if (ref.startsWith("#")) {
224225
// local ref

oauth2/core/src/intTest/java/com/dremio/iceberg/authmgr/oauth2/agent/OAuth2AgentAuth0IT.java renamed to oauth2/agent/src/intTest/java/com/dremio/iceberg/authmgr/oauth2/agent/OAuth2AgentAuth0IT.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919

20-
import com.dremio.iceberg.authmgr.oauth2.flow.TokensResult;
21-
import com.dremio.iceberg.authmgr.oauth2.test.ImmutableTestEnvironment.Builder;
22-
import com.dremio.iceberg.authmgr.oauth2.test.TestEnvironment;
23-
import com.dremio.iceberg.authmgr.oauth2.test.junit.EnumLike;
20+
import com.dremio.iceberg.authmgr.oauth2.agent.flow.TokensResult;
21+
import com.dremio.iceberg.authmgr.oauth2.agent.junit.EnumLike;
2422
import com.nimbusds.jwt.JWT;
2523
import com.nimbusds.jwt.JWTClaimsSet;
2624
import com.nimbusds.jwt.JWTParser;
@@ -109,7 +107,7 @@ private void introspectToken(AccessToken accessToken, TestEnvironment env) throw
109107
assertThat(claims.getStringClaim("scope")).contains("catalog");
110108
}
111109

112-
private static Builder envBuilder(
110+
private static ImmutableTestEnvironment.Builder envBuilder(
113111
GrantType initialGrantType, ClientAuthenticationMethod authenticationMethod) {
114112

115113
URI issuerUrl = URI.create(System.getenv(AUTH0_DOMAIN_ENV));
@@ -119,7 +117,7 @@ private static Builder envBuilder(
119117
? new Scope("catalog")
120118
: new Scope("catalog", "offline_access"); // request refresh token
121119

122-
return TestEnvironment.builder()
120+
return ImmutableTestEnvironment.builder()
123121
.serverRootUrl(issuerUrl)
124122
.authorizationServerUrl(issuerUrl)
125123
.grantType(initialGrantType)

0 commit comments

Comments
 (0)