Skip to content

Commit 7eef6a0

Browse files
mhlidddevflow.devflow-routing-intake
andauthored
Add Integration Specific Handling for Config Inversion Linter (#11074)
adding logback env var to supported-configurations.json adding spring-messaging-kotlin adding profiling config Merge branch 'master' into mhlidd/add_logback_config init adding tasks to gitlab job ensure GeneratedSupportedConfigurations is generated before the task runs Merge branch 'mhlidd/add_logback_config' into mhlidd/add_integration_gradle_task Merge branch 'master' into mhlidd/add_integration_gradle_task updating pr feedback updating PR comments 1 abstracting instrumentation checks into abstract class and extending from it Merge branch 'master' into mhlidd/add_integration_gradle_task Merge branch 'master' into mhlidd/add_integration_gradle_task fixing compilation errors from rebase addressing pr comments pt2 Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent d149028 commit 7eef6a0

File tree

2 files changed

+229
-4
lines changed

2 files changed

+229
-4
lines changed

buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,22 @@ class ConfigInversionLinter : Plugin<Project> {
1919
val logEnvVarUsages = registerLogEnvVarUsages(target, extension)
2020
val checkEnvVarUsage = registerCheckEnvironmentVariablesUsage(target)
2121
val checkConfigStrings = registerCheckConfigStringsTask(target, extension)
22+
val checkInstrumenterModule = registerCheckInstrumenterModuleConfigurations(target, extension)
23+
val checkDecoratorAnalytics = registerCheckDecoratorAnalyticsConfigurations(target, extension)
2224

2325
target.tasks.register("checkConfigurations") {
2426
group = "verification"
2527
description = "Runs all config inversion validation checks"
26-
dependsOn(logEnvVarUsages, checkEnvVarUsage, checkConfigStrings)
28+
dependsOn(logEnvVarUsages, checkEnvVarUsage, checkConfigStrings, checkInstrumenterModule, checkDecoratorAnalytics)
2729
}
2830
}
2931
}
3032

3133
// Data class for fields from generated class
32-
internal data class LoadedConfigFields(
34+
data class LoadedConfigFields(
3335
val supported: Set<String>,
34-
val aliasMapping: Map<String, String> = emptyMap()
36+
val aliasMapping: Map<String, String> = emptyMap(),
37+
val aliases: Map<String, List<String>> = emptyMap()
3538
)
3639

3740
// Cache for fields from generated class
@@ -57,7 +60,9 @@ internal fun loadConfigFields(
5760

5861
@Suppress("UNCHECKED_CAST")
5962
val aliasMappingMap = clazz.getField("ALIAS_MAPPING").get(null) as Map<String, String>
60-
LoadedConfigFields(supportedSet, aliasMappingMap)
63+
@Suppress("UNCHECKED_CAST")
64+
val aliasesMap = clazz.getField("ALIASES").get(null) as Map<String, List<String>>
65+
LoadedConfigFields(supportedSet, aliasMappingMap, aliasesMap)
6166
}.also { cachedConfigFields = it }
6267
}
6368
}
@@ -197,3 +202,53 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte
197202
}
198203
}
199204

205+
206+
/** Registers `checkInstrumenterModuleConfigurations` to verify each InstrumenterModule's integration name has proper entries in SUPPORTED and ALIASES. */
207+
private fun registerCheckInstrumenterModuleConfigurations(project: Project, extension: SupportedTracerConfigurations): TaskProvider<CheckInstrumenterModuleConfigTask> {
208+
val ownerPath = extension.configOwnerPath
209+
val generatedFile = extension.className
210+
211+
return project.tasks.register("checkInstrumenterModuleConfigurations", CheckInstrumenterModuleConfigTask::class.java) {
212+
group = "verification"
213+
description = "Validates that InstrumenterModule integration names have corresponding entries in SUPPORTED and ALIASES"
214+
215+
mainSourceSetOutput.from(ownerPath.map {
216+
project.project(it)
217+
.extensions.getByType<SourceSetContainer>()
218+
.named(SourceSet.MAIN_SOURCE_SET_NAME)
219+
.map { main -> main.output }
220+
})
221+
instrumentationFiles.from(project.fileTree(project.rootProject.projectDir) {
222+
include("dd-java-agent/instrumentation/**/src/main/java/**/*.java")
223+
})
224+
generatedClassName.set(generatedFile)
225+
errorHeader.set("\nFound InstrumenterModule integration names with missing SUPPORTED/ALIASES entries:")
226+
errorMessage.set("InstrumenterModule integration names are missing from SUPPORTED or ALIASES in '${extension.jsonFile.get()}'.")
227+
successMessage.set("All InstrumenterModule integration names have proper SUPPORTED and ALIASES entries.")
228+
}
229+
}
230+
231+
/** Registers `checkDecoratorAnalyticsConfigurations` to verify each BaseDecorator subclass's instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */
232+
private fun registerCheckDecoratorAnalyticsConfigurations(project: Project, extension: SupportedTracerConfigurations): TaskProvider<CheckDecoratorAnalyticsConfigTask> {
233+
val ownerPath = extension.configOwnerPath
234+
val generatedFile = extension.className
235+
236+
return project.tasks.register("checkDecoratorAnalyticsConfigurations", CheckDecoratorAnalyticsConfigTask::class.java) {
237+
group = "verification"
238+
description = "Validates that Decorator instrumentationNames have corresponding analytics entries in SUPPORTED and ALIASES"
239+
240+
mainSourceSetOutput.from(ownerPath.map {
241+
project.project(it)
242+
.extensions.getByType<SourceSetContainer>()
243+
.named(SourceSet.MAIN_SOURCE_SET_NAME)
244+
.map { main -> main.output }
245+
})
246+
instrumentationFiles.from(project.fileTree(project.rootProject.projectDir) {
247+
include("dd-java-agent/instrumentation/**/src/main/java/**/*.java")
248+
})
249+
generatedClassName.set(generatedFile)
250+
errorHeader.set("\nFound Decorator instrumentationNames with missing analytics SUPPORTED/ALIASES entries:")
251+
errorMessage.set("Decorator instrumentationNames are missing analytics entries from SUPPORTED or ALIASES in '${extension.jsonFile.get()}'.")
252+
successMessage.set("All Decorator instrumentationNames have proper analytics SUPPORTED and ALIASES entries.")
253+
}
254+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package datadog.gradle.plugin.config
2+
3+
import com.github.javaparser.ParserConfiguration
4+
import com.github.javaparser.StaticJavaParser
5+
import com.github.javaparser.ast.CompilationUnit
6+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
7+
import com.github.javaparser.ast.body.MethodDeclaration
8+
import com.github.javaparser.ast.expr.StringLiteralExpr
9+
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt
10+
import com.github.javaparser.ast.stmt.ReturnStmt
11+
import org.gradle.api.DefaultTask
12+
import org.gradle.api.GradleException
13+
import org.gradle.api.file.ConfigurableFileCollection
14+
import org.gradle.api.provider.Property
15+
import org.gradle.api.tasks.Input
16+
import org.gradle.api.tasks.InputFiles
17+
import org.gradle.api.tasks.TaskAction
18+
19+
/** Abstract base for tasks that scan instrumentation source files against the generated config class. */
20+
abstract class InstrumentationConfigCheckTask : DefaultTask() {
21+
@get:InputFiles
22+
abstract val mainSourceSetOutput: ConfigurableFileCollection
23+
24+
@get:InputFiles
25+
abstract val instrumentationFiles: ConfigurableFileCollection
26+
27+
@get:Input
28+
abstract val generatedClassName: Property<String>
29+
30+
@get:Input
31+
abstract val errorHeader: Property<String>
32+
33+
@get:Input
34+
abstract val errorMessage: Property<String>
35+
36+
@get:Input
37+
abstract val successMessage: Property<String>
38+
39+
@TaskAction
40+
fun execute() {
41+
val configFields = loadConfigFields(mainSourceSetOutput, generatedClassName.get())
42+
43+
val parserConfig = ParserConfiguration()
44+
parserConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8)
45+
StaticJavaParser.setConfiguration(parserConfig)
46+
47+
val repoRoot = project.rootProject.projectDir.toPath()
48+
val violations = instrumentationFiles.files.flatMap { file ->
49+
val rel = repoRoot.relativize(file.toPath()).toString()
50+
val cu: CompilationUnit = try {
51+
StaticJavaParser.parse(file)
52+
} catch (_: Exception) {
53+
return@flatMap emptyList()
54+
}
55+
collectPropertyViolations(configFields, rel, cu)
56+
}
57+
58+
if (violations.isNotEmpty()) {
59+
logger.error(errorHeader.get())
60+
violations.forEach { logger.lifecycle(it) }
61+
throw GradleException(errorMessage.get())
62+
} else {
63+
logger.info(successMessage.get())
64+
}
65+
}
66+
67+
protected abstract fun collectPropertyViolations(
68+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
69+
): List<String>
70+
71+
/** Collects violations for [key] against [supported] and [aliases], checking that all [expectedAliases] are values of that alias entry. */
72+
protected fun collectMissingKeysAndAliases(
73+
key: String,
74+
expectedAliases: List<String>,
75+
supported: Set<String>,
76+
aliases: Map<String, List<String>>,
77+
location: String,
78+
context: String
79+
): List<String> = buildList {
80+
if (key !in supported) {
81+
add("$location -> $context: '$key' is missing from SUPPORTED")
82+
}
83+
if (key !in aliases) {
84+
add("$location -> $context: '$key' is missing from ALIASES")
85+
} else {
86+
val aliasValues = aliases[key] ?: emptyList()
87+
for (expected in expectedAliases) {
88+
if (expected !in aliasValues) {
89+
add("$location -> $context: '$expected' is missing from ALIASES['$key']")
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
/** Checks that InstrumenterModule integration names have proper entries in SUPPORTED and ALIASES. */
97+
abstract class CheckInstrumenterModuleConfigTask : InstrumentationConfigCheckTask() {
98+
override fun collectPropertyViolations(
99+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
100+
): List<String> {
101+
val violations = mutableListOf<String>()
102+
103+
cu.findAll(ClassOrInterfaceDeclaration::class.java).forEach classLoop@{ classDecl ->
104+
val extendsModule = classDecl.extendedTypes.any { it.toString().startsWith("InstrumenterModule") }
105+
if (!extendsModule) return@classLoop
106+
107+
classDecl.findAll(ExplicitConstructorInvocationStmt::class.java)
108+
.filter { !it.isThis }
109+
.forEach { superCall ->
110+
val names = superCall.arguments
111+
.filterIsInstance<StringLiteralExpr>()
112+
.map { it.value }
113+
val line = superCall.range.map { it.begin.line }.orElse(1)
114+
115+
for (name in names) {
116+
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
117+
val enabledKey = "DD_TRACE_${normalized}_ENABLED"
118+
val context = "Integration '$name' (super arg)"
119+
val location = "$relativePath:$line"
120+
121+
violations.addAll(collectMissingKeysAndAliases(
122+
enabledKey,
123+
listOf("DD_TRACE_INTEGRATION_${normalized}_ENABLED", "DD_INTEGRATION_${normalized}_ENABLED"),
124+
configFields.supported, configFields.aliases, location, context
125+
))
126+
}
127+
}
128+
}
129+
130+
return violations
131+
}
132+
}
133+
134+
/** Checks that Decorator instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */
135+
abstract class CheckDecoratorAnalyticsConfigTask : InstrumentationConfigCheckTask() {
136+
override fun collectPropertyViolations(
137+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
138+
): List<String> {
139+
val violations = mutableListOf<String>()
140+
141+
cu.findAll(MethodDeclaration::class.java)
142+
.filter { it.nameAsString == "instrumentationNames" && it.parameters.isEmpty() }
143+
.forEach { method ->
144+
val names = method.findAll(ReturnStmt::class.java).flatMap { ret ->
145+
ret.expression.map { it.findAll(StringLiteralExpr::class.java).map { s -> s.value } }
146+
.orElse(emptyList())
147+
}
148+
val line = method.range.map { it.begin.line }.orElse(1)
149+
150+
for (name in names) {
151+
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
152+
val context = "Decorator instrumentationName '$name'"
153+
val location = "$relativePath:$line"
154+
155+
violations.addAll(collectMissingKeysAndAliases(
156+
"DD_TRACE_${normalized}_ANALYTICS_ENABLED",
157+
listOf("DD_${normalized}_ANALYTICS_ENABLED"),
158+
configFields.supported, configFields.aliases, location, context
159+
))
160+
violations.addAll(collectMissingKeysAndAliases(
161+
"DD_TRACE_${normalized}_ANALYTICS_SAMPLE_RATE",
162+
listOf("DD_${normalized}_ANALYTICS_SAMPLE_RATE"),
163+
configFields.supported, configFields.aliases, location, context
164+
))
165+
}
166+
}
167+
168+
return violations
169+
}
170+
}

0 commit comments

Comments
 (0)