/* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ buildscript { ext { shadowGradlePluginVersion = '8.3.5' } repositories { mavenCentral() maven { url = 'https://plugins.gradle.org/m2/' } } } plugins { id "de.undercouch.download" version "4.0.4" id "com.dorongold.task-tree" version "2.1.0" } apply from: "${projectDir}/x-pack/distributions/internal/observabilitySRE/build-ext.gradle" apply plugin: 'de.undercouch.download' apply from: "rubyUtils.gradle" import de.undercouch.gradle.tasks.download.Download import groovy.json.JsonSlurper import org.logstash.gradle.tooling.ListProjectDependencies import org.logstash.gradle.tooling.SignAliasDefinitions import org.logstash.gradle.tooling.ToolingUtils import org.logstash.gradle.tooling.SnapshotArtifactURLs import java.net.HttpURLConnection allprojects { group = 'org.logstash' apply plugin: 'java' apply plugin: 'idea' apply plugin: 'java-library' java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.withType(JavaCompile).configureEach { options.compilerArgs.add("-Xlint:all") options.compilerArgs.add("-Xlint:-processing") options.compilerArgs.add("-Werror") } tasks.withType(Javadoc).configureEach { if (JavaVersion.current().compareTo(JavaVersion.VERSION_14) > 0) { // with JDK 15 the -Xwerror undocumented feature becomes official with switch -Werror options.addBooleanOption("Werror", true) } else { options.addBooleanOption("Xwerror", true) } options.addBooleanOption("Xdoclint:all,-missing", true) if (JavaVersion.current().compareTo(JavaVersion.VERSION_1_9) > 0) { options.addBooleanOption("html5", true) } } tasks.withType(Copy).configureEach { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } clean { delete "${projectDir}/out/" } tasks.withType(Test).configureEach { // Add Exports to enable tests to run in JDK17 jvmArgs = [ "-Djruby.compile.invokedynamic=true", "-Dlog4j2.isThreadContextMapInheritable=true", "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED" ] maxHeapSize = "2g" //https://stackoverflow.com/questions/3963708/gradle-how-to-display-test-results-in-the-console-in-real-time testLogging { // set options for log level LIFECYCLE events "passed", "skipped", "failed", "standardOut" showExceptions = true exceptionFormat = "full" showCauses = true showStackTraces = true enableAssertions = false // set options for log level DEBUG and INFO debug { events "started", "passed", "skipped", "failed", "standardOut", "standardError" exceptionFormat = "full" } info.events = debug.events info.exceptionFormat = debug.exceptionFormat } addTestListener(new TestListener() { void beforeSuite(TestDescriptor suite) {} void afterSuite(TestDescriptor desc, TestResult result) { if (!desc.parent) { // will match the outermost suite def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" def startItem = '| ', endItem = ' |' def repeatLength = startItem.length() + output.length() + endItem.length() println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) } } void beforeTest(TestDescriptor testDescriptor) {} void afterTest(TestDescriptor testDescriptor, TestResult result) {} }) } } subprojects { repositories { mavenCentral() maven { url = 'https://plugins.gradle.org/m2/' } } tasks.register("generateLicenseReport", ListProjectDependencies) { } } version = versionMap['logstash-core'] tasks.register("configureArchitecture") { String arch = System.properties['os.arch'] String beatsArch = arch String esArch = arch String osName = (System.properties['os.name'] ==~ /Mac OS X/) ? "darwin" : "linux" // For aarch64 architectures, beats and elasticsearch name their artifacts differently if (arch == "aarch64") { beatsArch=(osName == "darwin") ? "aarch64" : "arm64" esArch="aarch64" } else if (arch == "amd64") { beatsArch="x86_64" esArch="x86_64" } project.ext.set("beatsArchitecture", "${osName}-${beatsArch}") project.ext.set("esArchitecture", "${osName}-${esArch}") } tasks.register("configureArtifactInfo") { dependsOn configureArchitecture description = "Set the url to download stack artifacts for select stack version" def projectRef = project doLast { def splitVersion = version.split('\\.') int major = splitVersion[0].toInteger() int minor = splitVersion[1].toInteger() String branch = "${major}.${minor}" String fallbackMajorX = "${major}.x" boolean isFallBackPreviousMajor = minor - 1 < 0 String fallbackBranch = isFallBackPreviousMajor ? "${major-1}.x" : "${major}.${minor-1}" def qualifiedVersion = "" for (b in [branch, fallbackMajorX, fallbackBranch]) { def url = "https://storage.googleapis.com/artifacts-api/snapshots/${b}.json" try { def snapshotInfo = new JsonSlurper().parseText(url.toURL().text) qualifiedVersion = snapshotInfo.version println "ArtifactInfo version: ${qualifiedVersion}" break } catch (Exception e) { println "Failed to fetch branch ${branch} from ${url}: ${e.message}" } } projectRef.ext.set("artifactApiVersion", qualifiedVersion) } } tasks.register("markAliasDefinitions", SignAliasDefinitions) { description = "Create an hashes aliases file from original aliases yml definition" hashedFile = layout.buildDirectory.file("plugin_aliases_hashed.yml").get().asFile } tasks.register("markTestAliasDefinitions", SignAliasDefinitions) { description = "Create an hashes aliases file for testing aliases yml definition" stage SignAliasDefinitions.Stage.test hashedFile = layout.buildDirectory.file("plugin_aliases_hashed_test.yml").get().asFile } tasks.register("copyPluginAlias", Copy) { description = "Copy the marked plugin_aliases.yml file to destination folders" dependsOn = [copyPluginAlias_ruby, copyPluginAlias_java] } tasks.register("copyPluginAlias_ruby", Copy) { description = "Copy the marked plugin_aliases.yml file to destination folders" dependsOn "markAliasDefinitions" def aliasFile = layout.buildDirectory.file("plugin_aliases_hashed.yml") inputs.file(aliasFile) from(aliasFile) { rename "plugin_aliases_hashed.yml", "plugin_aliases.yml" } into "lib/pluginmanager/" } tasks.register("copyPluginAlias_java", Copy) { description = "Copy the marked plugin_aliases.yml file to destination folders" dependsOn "markAliasDefinitions" def aliasFile = layout.buildDirectory.file("plugin_aliases_hashed.yml") inputs.file(aliasFile) from(aliasFile) { rename "plugin_aliases_hashed.yml", "plugin_aliases.yml" } into "logstash-core/src/main/resources/org/logstash/plugins/" } tasks.register("copyPluginTestAlias") { description = "Copy the marked test plugin_aliases.yml file to destination folders" dependsOn = [copyPluginTestAlias_ruby, copyPluginTestAlias_java] } tasks.register("copyPluginTestAlias_ruby", Copy) { description = "Copy the marked test plugin_aliases.yml file into Ruby's plugin_manager specs" dependsOn "markTestAliasDefinitions" def aliasTestFile = layout.buildDirectory.file("plugin_aliases_hashed_test.yml") inputs.file(aliasTestFile) from(aliasTestFile) { rename "plugin_aliases_hashed_test.yml", "plugin_aliases.yml" } into "spec/unit/plugin_manager/" } tasks.register("copyPluginTestAlias_java", Copy) { description = "Copy the marked test plugin_aliases.yml file into logstash-core's test resources" dependsOn "markTestAliasDefinitions" def aliasTestFile = layout.buildDirectory.file("plugin_aliases_hashed_test.yml") inputs.file(aliasTestFile) from(aliasTestFile) { rename "plugin_aliases_hashed_test.yml", "plugin_aliases.yml" } into "logstash-core/src/test/resources/org/logstash/plugins/" } project(':logstash-core').tasks.named('processResources').configure { dependsOn(copyPluginAlias) } project(':logstash-core').tasks.named('processTestResources').configure { dependsOn(copyPluginTestAlias) } // Tasks tasks.register("printStackVersion") { description = "Print the stack version from logstash-plugins CI" doLast { def releaseTrack = gradle.ext.versions['logstash-release-track'] def url = new URL("https://raw.githubusercontent.com/logstash-plugins/.ci/1.x/logstash-versions.yml") def remoteVersions = new org.yaml.snakeyaml.Yaml().load(url.text) println remoteVersions['snapshots'][releaseTrack] } } clean { delete "${projectDir}/Gemfile" delete "${projectDir}/Gemfile.lock" delete "${projectDir}/vendor" delete "${projectDir}/.bundle" delete "${projectDir}/qa/integration/Gemfile.lock" delete "${projectDir}/qa/integration/.bundle" delete "${projectDir}/build/licenseReportFolders.txt" delete "${projectDir}/build/rubyDependencies.csv" delete "${projectDir}/lib/pluginmanager/plugin_aliases.yml" delete "${projectDir}/spec/unit/plugin_manager/plugin_aliases.yml" delete "${projectDir}/logstash-core/build/resources/test/org/logstash/plugins/plugin_aliases.yml" delete "${projectDir}/logstash-core/build/resources/main/org/logstash/plugins/plugin_aliases.yml" delete "${projectDir}/logstash-core/src/test/resources/org/logstash/plugins/plugin_aliases.yml" delete "${projectDir}/logstash-core/src/main/resources/org/logstash/plugins/plugin_aliases.yml" } def assemblyDeps = [downloadAndInstallJRuby, assemble] + subprojects.collect { it.tasks.findByName("assemble") } tasks.register("bootstrap") { dependsOn assemblyDeps doLast { setupJruby(projectDir, buildDir) } } tasks.register("dockerBootstrap") { description = "Docker bootstrap ensures env2yaml java is compiled and staged for inclusion in tarballs" dependsOn bootstrap dependsOn ':docker:data:logstash:env2yaml:compileJava' } tasks.register("installDefaultGems") { dependsOn bootstrap doLast { rake(projectDir, buildDir, 'plugin:install-default') } } tasks.register("installDevelopmentGems") { dependsOn bootstrap doLast { rake(projectDir, buildDir, 'plugin:install-development-dependencies') } } tasks.register("artifactDocker") { description = "Build docker image" dependsOn dockerBootstrap dependsOn copyAllJdks doLast { rake(projectDir, buildDir, 'artifact:docker') } } tasks.register("artifactDockerOss") { description = "Build OSS docker image" dependsOn dockerBootstrap dependsOn copyAllJdks doLast { rake(projectDir, buildDir, 'artifact:docker_oss') } } tasks.register("artifactDockerWolfi") { description = "Build Wolfi docker image" dependsOn dockerBootstrap dependsOn copyAllJdks doLast { rake(projectDir, buildDir, 'artifact:docker_wolfi') } } tasks.register("artifactDockerIronbank") { description = "Build Ironbank docker image" dependsOn dockerBootstrap dependsOn copyAllJdks doLast { rake(projectDir, buildDir, 'artifact:docker_ironbank') } } tasks.register("artifactDockerfiles") { description = "Generate all Dockerfiles and build contexts" doLast { rake(projectDir, buildDir, 'artifact:dockerfiles') } } tasks.register("artifactAll", Exec) { description = "Build all artifacts except docker" dependsOn bootstrap dependsOn copyAllJdks workingDir projectDir environment 'GEM_HOME', "${projectDir}/vendor/bundle/jruby/3.4.0" environment 'GEM_PATH', "${projectDir}/vendor/bundle/jruby/3.4.0" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "rake", "artifact:all" } tasks.register("artifactDeb", Exec) { description = "Build DEB package" dependsOn bootstrap dependsOn copyAllJdks workingDir projectDir environment 'ARCH', System.getenv("ARCH") ?: "x86_64" environment 'GEM_HOME', "${projectDir}/vendor/bundle/jruby/3.4.0" environment 'GEM_PATH', "${projectDir}/vendor/bundle/jruby/3.4.0" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "rake", "artifact:deb" } tasks.register("artifactRpm", Exec) { description = "Build RPM package" dependsOn bootstrap dependsOn copyAllJdks workingDir projectDir environment 'ARCH', System.getenv("ARCH") ?: "x86_64" environment 'GEM_HOME', "${projectDir}/vendor/bundle/jruby/3.4.0" environment 'GEM_PATH', "${projectDir}/vendor/bundle/jruby/3.4.0" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "rake", "artifact:rpm" } tasks.register("compileGrammar") { dependsOn bootstrap doLast { rake(projectDir, buildDir, 'compile:grammar') } } tasks.register("artifactDockerOnly") { description = "Build docker image without OSS" dependsOn dockerBootstrap dependsOn copyAllJdks doLast { rake(projectDir, buildDir, 'artifact:docker_only') } } tasks.register("generatePluginsVersion") { description = "Generate plugins version documentation" dependsOn bootstrap dependsOn installDefaultGems doLast { rake(projectDir, buildDir, 'generate_plugins_version') } } tasks.register("artifactDockerObservabilitySRE") { dependsOn bootstrap dependsOn "copyAllJdks" inputs.files fileTree("${projectDir}/rakelib") inputs.files fileTree("${projectDir}/bin") inputs.files fileTree("${projectDir}/config") inputs.files fileTree("${projectDir}/lib") inputs.files fileTree("${projectDir}/logstash-core-plugin-api") inputs.files fileTree("${projectDir}/logstash-core/lib") inputs.files fileTree("${projectDir}/logstash-core/src") inputs.files fileTree("${projectDir}/x-pack") outputs.files fileTree("${buildDir}") { include "Dockerfile-observability-sre" include "logstash-observability-sre-${project.version}-SNAPSHOT-linux-*.tar.gz" include "logstash-observability-sre-${project.version}-SNAPSHOT-docker-build-context.tar.gz" include "plugin_aliases_hashed.yml" include "jdk-*-linux-*.tar.gz" } doFirst { if (!fedrampHighMode) { logger.error("NOT in Fedramp High mode. Aborting.") throw new GradleException("cannot build docker artifact for observabilitySRE without `-PfedrampHighMode=true`") } } doLast { rake(projectDir, buildDir, 'artifact:docker_observabilitySRE') } } tasks.register("assembleTarDistribution") { dependsOn bootstrap dependsOn "copyAllJdks" inputs.files fileTree("${projectDir}/rakelib") inputs.files fileTree("${projectDir}/bin") inputs.files fileTree("${projectDir}/config") inputs.files fileTree("${projectDir}/lib") inputs.files fileTree("${projectDir}/logstash-core-plugin-api") inputs.files fileTree("${projectDir}/logstash-core/lib") inputs.files fileTree("${projectDir}/logstash-core/src") inputs.files fileTree("${projectDir}/x-pack") outputs.files file("${buildDir}/logstash-${project.version}-SNAPSHOT.tar.gz") doLast { rake(projectDir, buildDir, 'artifact:bundle_jdk_tar') } } evaluationDependsOn(":logstash-core") project(":logstash-core") { ["rubyTests", "test"].each { tsk -> tasks.getByPath(":logstash-core:" + tsk).configure { dependsOn copyPluginTestAlias dependsOn installDevelopmentGems dependsOn installDefaultGems } } } def logstashBuildDir = "${buildDir}/logstash-${project.version}-SNAPSHOT" tasks.register("unpackTarDistribution", Copy) { dependsOn assembleTarDistribution def tar = file("${buildDir}/logstash-${project.version}-SNAPSHOT.tar.gz") inputs.files tar outputs.files fileTree(logstashBuildDir) from tarTree(tar) into {buildDir} } def qaBuildPath = "${buildDir}/qa/integration" def qaVendorPath = "${qaBuildPath}/vendor" tasks.register("installIntegrationTestGems") { dependsOn assembleTarDistribution dependsOn unpackTarDistribution def gemfilePath = file("${projectDir}/qa/integration/Gemfile") inputs.files gemfilePath inputs.files file("${projectDir}/qa/integration/integration_tests.gemspec") inputs.files file("${logstashBuildDir}/Gemfile") inputs.files file("${logstashBuildDir}/Gemfile.lock") inputs.files file("${logstashBuildDir}/logstash-core/logstash-core.gemspec") outputs.files fileTree("${qaVendorPath}") outputs.files file("${projectDir}/qa/integration/Gemfile.lock") doLast { bundleQAGems(projectDir, qaBuildPath) } } tasks.register("prepareFilebeatDownload") { dependsOn configureArtifactInfo def projectRef = project doLast { String beatsVersion = projectRef.ext.get("artifactApiVersion") String downloadedFilebeatName = "filebeat-${beatsVersion}-${projectRef.ext.get("beatsArchitecture")}" projectRef.ext.set("unpackedFilebeatName", downloadedFilebeatName) def res = SnapshotArtifactURLs.packageUrls("beats", beatsVersion, downloadedFilebeatName) projectRef.ext.set("filebeatSnapshotUrl", System.getenv("FILEBEAT_SNAPSHOT_URL") ?: res.packageUrl) projectRef.ext.set("filebeatDownloadLocation", "${projectDir}/build/${downloadedFilebeatName}.tar.gz") } } def verifyPackageSHA(String artifactProject, String version, String packageName) { def res = SnapshotArtifactURLs.packageUrls(artifactProject, version, packageName) String remoteSHACode = res.packageShaUrl.toURL().text.split(' ')[0] def localArchive = new File("${projectDir}/build/${packageName}.tar.gz") if (localArchive.exists()) { ant.checksum(file: localArchive, algorithm: "SHA-512", forceoverwrite: true) String localSHA = new File("${projectDir}/build/${packageName}.tar.gz.SHA-512").text.trim() if (localSHA != remoteSHACode) { println "${artifactProject} package calculated fingerprint is different from remote, deleting local archive" delete(localArchive) } else { println "Local ${artifactProject} package is already the latest" } } } tasks.register("checkFilebeatSHA") { dependsOn configureArtifactInfo description = "Download Filebeat version remote's fingerprint file" def projectRef = project doLast { String beatsVersion = projectRef.ext.get("artifactApiVersion") String downloadedFilebeatName = "filebeat-${beatsVersion}-${projectRef.ext.get("beatsArchitecture")}" verifyPackageSHA("beats", beatsVersion, downloadedFilebeatName) } } tasks.register("downloadFilebeat", Download) { dependsOn prepareFilebeatDownload, checkFilebeatSHA description = "Download Filebeat Snapshot for current branch version: ${version}" project.ext.set("versionFound", true) inputs.file("${projectDir}/versions.yml") def projectRef = project // Use lazy configuration to get values after prepareFilebeatDownload runs src { projectRef.ext.filebeatSnapshotUrl } dest { new File(projectRef.ext.filebeatDownloadLocation) } onlyIfNewer true retries 3 doLast { System.out.println "Downloaded to ${projectRef.ext.filebeatDownloadLocation}" } } tasks.register("deleteLocalFilebeat", Delete) { delete ('./build/filebeat') } tasks.register("copyFilebeat") { dependsOn downloadFilebeat inputs.files(tasks.named("downloadFilebeat")) outputs.dir('./build/filebeat') mustRunAfter tasks.named("unpackTarDistribution") doLast { delete('./build/filebeat') copy { from tarTree(resources.gzip(project.ext.filebeatDownloadLocation)) into "./build/" } file("./build/${project.ext.unpackedFilebeatName}").renameTo('./build/filebeat') System.out.println "Unzipped ${project.ext.filebeatDownloadLocation} to ./build/filebeat" } } tasks.register("checkEsSHA") { dependsOn configureArtifactInfo description = "Download ES version remote's fingerprint file" def projectRef = project doLast { String esVersion = projectRef.ext.get("artifactApiVersion") String downloadedElasticsearchName = "elasticsearch-${esVersion}-${projectRef.ext.get("esArchitecture")}" verifyPackageSHA("elasticsearch", esVersion, downloadedElasticsearchName) } } tasks.register("prepareEsDownload") { dependsOn configureArtifactInfo def projectRef = project doLast { String esVersion = projectRef.ext.get("artifactApiVersion") String downloadedElasticsearchName = "elasticsearch-${esVersion}-${projectRef.ext.get("esArchitecture")}" projectRef.ext.set("unpackedElasticsearchName", "elasticsearch-${esVersion}") def res = SnapshotArtifactURLs.packageUrls("elasticsearch", esVersion, downloadedElasticsearchName) projectRef.ext.set("elasticsearchSnapshotURL", System.getenv("ELASTICSEARCH_SNAPSHOT_URL") ?: res.packageUrl) projectRef.ext.set("elasticsearchDownloadLocation", "${projectDir}/build/${downloadedElasticsearchName}.tar.gz") } } tasks.register("downloadEs", Download) { dependsOn = [prepareEsDownload, checkEsSHA] description = "Download ES Snapshot for current branch version: ${version}" inputs.file("${projectDir}/versions.yml") def projectRef = project src { projectRef.ext.elasticsearchSnapshotURL } dest { new File(projectRef.ext.elasticsearchDownloadLocation) } onlyIfNewer true overwrite false retries 3 doLast { System.out.println "Downloaded to ${projectRef.ext.elasticsearchDownloadLocation}" } } tasks.register("deleteLocalEs", Delete) { delete ('./build/elasticsearch') } tasks.register("copyEs") { dependsOn downloadEs inputs.files(tasks.named("downloadEs")) outputs.dir('./build/elasticsearch') mustRunAfter tasks.named("unpackTarDistribution") doLast { println "copyEs executing.." delete('./build/elasticsearch') copy { from tarTree(resources.gzip(project.ext.elasticsearchDownloadLocation)) into "./build/" } file("./build/${project.ext.unpackedElasticsearchName}").renameTo('./build/elasticsearch') println "Unzipped ${project.ext.elasticsearchDownloadLocation} to ./build/elasticsearch" } } def rubyIntegrationSpecs = project.hasProperty("rubyIntegrationSpecs") ? ((String) project.property("rubyIntegrationSpecs")).split(/\s+/).join(",") : "specs/**/*_spec.rb" def integrationTestPwd = "${projectDir}/qa/integration" evaluationDependsOn(":logstash-integration-tests") project(":logstash-integration-tests") { tasks.getByPath(":logstash-integration-tests:integrationTests").configure { systemProperty 'org.logstash.integration.specs', rubyIntegrationSpecs environment "FEATURE_FLAG", System.getenv('FEATURE_FLAG') workingDir integrationTestPwd dependsOn installIntegrationTestGems dependsOn copyProductionLog4jConfiguration } } tasks.register("runIntegrationTests") { dependsOn ":logstash-integration-tests:integrationTests" dependsOn copyEs dependsOn copyFilebeat shouldRunAfter ":logstash-core:test" } tasks.register("installAcceptanceTestGems", Exec) { dependsOn bootstrap inputs.file("${projectDir}/qa/Gemfile") outputs.file("${projectDir}/qa/Gemfile.lock") workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "install" } tasks.register("runAcceptanceTests", Exec) { dependsOn installAcceptanceTestGems workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" // NOTE: We sub-process here to avoid issues in java 21+ with Jruby whereby access to some modules that // are required for file descriptors to be properly inherited when ruby does sub-processing. // https://github.com/elastic/logstash/pull/18536#discussion_r2655013858 commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rake", "qa:acceptance:all" } // Docker acceptance tests - reuses installAcceptanceTestGems for gem installation tasks.register("runDockerOssTests", Exec) { description = "Run OSS docker acceptance tests" dependsOn installAcceptanceTestGems dependsOn artifactDockerOss workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "docker/spec/oss" } tasks.register("runDockerFullTests", Exec) { description = "Run full docker acceptance tests" dependsOn installAcceptanceTestGems dependsOn artifactDocker workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "docker/spec/full" } tasks.register("runDockerWolfiTests", Exec) { description = "Run Wolfi docker acceptance tests" dependsOn installAcceptanceTestGems dependsOn artifactDockerWolfi workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "docker/spec/wolfi" } tasks.register("runAllDockerTests", Exec) { description = "Run all docker acceptance tests" dependsOn installAcceptanceTestGems dependsOn artifactDockerOnly workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "docker/spec" } tasks.register("runDockerIronbankTests", Exec) { description = "Run Ironbank docker acceptance tests" dependsOn installAcceptanceTestGems dependsOn artifactDockerIronbank workingDir "${projectDir}/qa" environment "BUNDLE_PATH", "vendor/bundle" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "docker/spec/ironbank" } // Serverless integration tests tasks.register("runServerlessDLQTests", Exec) { description = "Run serverless DLQ integration tests" dependsOn installIntegrationTestGems dependsOn unpackTarDistribution dependsOn ':logstash-core:copyGemjar' workingDir "${projectDir}/qa/integration" environment "BUNDLE_PATH", "${buildDir}/qa/integration/vendor" environment "SERVERLESS", "true" commandLine "${projectDir}/vendor/jruby/bin/jruby", "-S", "bundle", "exec", "rspec", "-fd", "specs/dlq_spec.rb", "-e", "using pipelines.yml" } tasks.register("generateLicenseReport", JavaExec) { dependsOn generateLicenseReportInputs dependsOn ":dependencies-report:assemble" def jarFile = project('dependencies-report').layout.buildDirectory.dir("libs").get().asFile.toString() + "/dependencies-report.jar" String licenseReportInputCSV = project.hasProperty("licenseReportInputCSV") ? project.property("licenseReportInputCSV") : "build/dependencies.csv.ruby" String licenseReportOutputCSV = project.hasProperty("licenseReportOutputCSV") ? project.property("licenseReportOutputCSV") : "build/dependencies.csv" String noticePath = "NOTICE.txt" classpath = project.files([jarFile]) mainClass = "org.logstash.dependencies.Main" args licenseReportInputCSV, layout.buildDirectory.file("licenseReportFolders.txt").get().asFile.toString(), licenseReportOutputCSV, noticePath } tasks.register("generateLicenseReportInputs") { dependsOn subprojects.generateLicenseReport doLast { // write location of all license reports for subprojects containing artifacts that are distributed to single file StringBuilder licenseReportFolders = new StringBuilder() subprojects.findAll { s1 -> !s1.hasProperty("isDistributedArtifact") || s1.property("isDistributedArtifact") == 'true' }.each { s -> s.tasks.findAll { t2 -> t2.getName() == "generateLicenseReport" }.each { t3 -> licenseReportFolders.append(t3.reportDir.toString() + "\n") } } if (gradle.startParameter.taskNames.contains("generateLicenseReport")) { def buildDirFile = layout.buildDirectory.get().asFile if (!buildDirFile.exists()) { buildDirFile.mkdirs() } def licenseReportPath = buildDirFile.toString() + "/licenseReportFolders.txt" def licenseReportFolder = new File(licenseReportPath) licenseReportFolder.delete() licenseReportFolder = new File(licenseReportPath) licenseReportFolder.createNewFile() if (licenseReportFolder.canWrite()) { licenseReportFolder.text = licenseReportFolders.toString() } } } } bootstrap.dependsOn assemblyDeps // FIXME: adding the integration tests task to check will mean // that any registered task will be evaluated. This creates an issue // where the downloadES task may throw an error on versions where // Elasticsearch doesn't yet have a build we can fetch // So for now we'll remove this to unblock builds, but finding a way // to compartimentalize failures is needed going forward //check.dependsOn runIntegrationTest def selectArch() { // 1. Check Gradle project property (e.g., -Pjdk_arch=arm64) if (project.ext.has("jdk_arch")) { return project.ext.jdk_arch } // 2. Check ARCH environment variable (used in CI for cross-compilation) // Matches rake pattern: x86_64 → x86_64, anything else (aarch64, arm64, etc.) → arm64 String envArch = System.getenv("ARCH") if (envArch) { return (envArch == "x86_64") ? "x86_64" : "arm64" } // 3. Fall back to host architecture String cpu_arch = System.properties["os.arch"] switch (cpu_arch) { case "amd64": case "x86_64": return "x86_64" case "aarch64": case "arm64": return "arm64" default: throw new IllegalArgumentException("Can't handle os.arch of type $cpu_arch") } } class JDKDetails { final String revision final String build final String vendor final int major private final String osName private final String extension final String localPackageName final String unpackedJdkName private String arch JDKDetails(bundledJdk, osName, jdkArch) { revision = bundledJdk.revision build = bundledJdk.build vendor = bundledJdk.vendor major = revision.split('\\.').first() as int this.osName = osName switch (osName) { case "windows": extension = "zip" break default: extension = "tar.gz" } arch = parseJdkArchitecture(jdkArch) unpackedJdkName = "jdk-${revision}-${osName}" localPackageName = "${unpackedJdkName}-${arch}.${extension}" } String createDownloadUrl() { return createElasticCatalogDownloadUrl() } private String createElasticCatalogDownloadUrl() { // Ask details to catalog https://jvm-catalog.elastic.co/jdk and return the url to download the JDK // arch x86_64 is default, aarch64 if macos or linux def url = "https://jvm-catalog.elastic.co/jdk/adoptiumjdk-${revision}+${build}-${osName}" // Append the cpu's arch only if not x86_64, which is the default if (arch == "aarch64") { url += "-${arch}" } println "Retrieving JDK location URL from Elastic catalog..." def catalogMetadataUrl = URI.create(url).toURL() int maxAttempts = 5 long retryDelayMillis = 3000 int connectTimeoutMillis = 15000 int readTimeoutMillis = 60000 int lastResponseCode = -1 String lastResponseMessage = null String lastErrorBody = null Exception lastException = null for (int attempt = 1; attempt <= maxAttempts; attempt++) { HttpURLConnection catalogConnection = null try { catalogConnection = catalogMetadataUrl.openConnection() as HttpURLConnection catalogConnection.requestMethod = 'GET' catalogConnection.connectTimeout = connectTimeoutMillis catalogConnection.readTimeout = readTimeoutMillis int responseCode = catalogConnection.responseCode if (responseCode == 200) { def metadataRetrieved = catalogConnection.inputStream.withCloseable { stream -> stream.getText('UTF-8') } println "Retrieved!" def catalogMetadata = new JsonSlurper().parseText(metadataRetrieved) validateMetadata(catalogMetadata) return catalogMetadata.url } lastResponseCode = responseCode lastResponseMessage = catalogConnection.responseMessage lastErrorBody = catalogConnection.errorStream?.withCloseable { stream -> stream.getText('UTF-8') }?.trim() lastException = null def statusMessage = lastResponseMessage ? "${responseCode} ${lastResponseMessage}" : "${responseCode}" if (lastErrorBody) { statusMessage += ": ${lastErrorBody.take(200)}" } if (attempt < maxAttempts) { println "Attempt ${attempt} of ${maxAttempts} failed with HTTP ${statusMessage}. Retrying..." try { Thread.sleep(retryDelayMillis) } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt() throw new GradleException("Interrupted while retrying Elastic JVM catalog metadata request", interruptedException) } } } catch (Exception e) { lastException = e lastResponseCode = -1 lastResponseMessage = null lastErrorBody = null if (attempt < maxAttempts) { println "Attempt ${attempt} of ${maxAttempts} failed: ${e.message}. Retrying..." try { Thread.sleep(retryDelayMillis) } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt() throw new GradleException("Interrupted while retrying Elastic JVM catalog metadata request", interruptedException) } } } finally { catalogConnection?.disconnect() } } def requestedArtifact = "adoptiumjdk-${revision}+${build}-${osName}${arch == "aarch64" ? "-${arch}" : ""}" if (lastResponseCode == 404) { throw new GradleException("Elastic JVM catalog returned 404 for ${requestedArtifact}. Verify the artifact exists.", lastException) } if (lastResponseCode != -1) { def statusMessage = lastResponseMessage ? "${lastResponseCode} ${lastResponseMessage}" : "${lastResponseCode}" if (lastErrorBody) { statusMessage += " (${lastErrorBody.take(200)})" } throw new GradleException("Failed to fetch Elastic JVM catalog metadata for ${requestedArtifact}. Last HTTP response: ${statusMessage}", lastException) } if (lastException != null) { throw new GradleException("Failed to fetch Elastic JVM catalog metadata for ${requestedArtifact}: ${lastException.message}", lastException) } throw new GradleException("Failed to fetch Elastic JVM catalog metadata for ${requestedArtifact}.") } //Verify that the artifact metadata correspond to the request, if not throws an error private void validateMetadata(Map metadata) { if (metadata.version != revision) { throw new GradleException("Expected to retrieve a JDK for version ${revision} but received: ${metadata.version}") } if (!isSameArchitecture(metadata.architecture)) { throw new GradleException("Expected to retrieve a JDK for architecture ${arch} but received: ${metadata.architecture}") } } private boolean isSameArchitecture(String metadataArch) { if (arch == 'x64') { return metadataArch == 'x86_64' } return metadataArch == arch } private String parseJdkArchitecture(String jdkArch) { switch (jdkArch) { case "x86_64": return "x64" break case "arm64": return "aarch64" break default: throw new GradleException("Can't handle CPU architechture: ${jdkArch}") } } } tasks.register("lint") { description = "Lint Ruby source files. Use -PrubySource=file1.rb,file2.rb to specify files" dependsOn installDevelopmentGems doLast { if (project.hasProperty("rubySource")) { // Split the comma-separated files and pass them as separate arguments def files = project.property("rubySource").split(",") rake(projectDir, buildDir, "lint:report", *files) } else { rake(projectDir, buildDir, "lint:report") } } } def getTargetPlatforms() { String arch = selectArch() if (arch == "x86_64") { return [[os: "linux", arch: "x86_64"], [os: "windows", arch: "x86_64"], [os: "darwin", arch: "x86_64"]] } else { return [[os: "linux", arch: "arm64"], [os: "darwin", arch: "arm64"]] } } ["linux", "windows", "darwin"].each { osName -> ["x86_64", "arm64"].each { archName -> if (osName == "windows" && archName == "arm64") { return } String taskSuffix = "${osName.capitalize()}${archName == 'x86_64' ? 'X64' : 'Arm64'}" String jdkFolderName = ToolingUtils.jdkFolderName(osName, archName) tasks.register("downloadJdk${taskSuffix}", Download) { def jdkDetails = new JDKDetails(gradle.ext.versions.bundled_jdk, osName, archName) // Use provider for lazy evaluation - only resolve URL when task executes src provider { System.getenv("JDK_URL") ?: jdkDetails.createDownloadUrl() } onlyIfNewer true overwrite false retries 3 quiet true inputs.file("${projectDir}/versions.yml") dest new File("${projectDir}/build/${jdkDetails.localPackageName}") doFirst { mkdir "${projectDir}/${jdkFolderName}" } } if (osName == "windows") { tasks.register("unpackJdk${taskSuffix}", Copy) { dependsOn "downloadJdk${taskSuffix}" def jdkDetails = new JDKDetails(gradle.ext.versions.bundled_jdk, osName, archName) String rootName = null from(zipTree("${projectDir}/build/${jdkDetails.localPackageName}")) { eachFile { fcd -> rootName = rootName ?: fcd.relativePath.segments[0] fcd.relativePath = new RelativePath(true, fcd.relativePath.segments.drop(1)) } } into "${projectDir}/${jdkFolderName}" doFirst { delete "${projectDir}/${jdkFolderName}" } doLast { delete "${projectDir}/${jdkFolderName}/$rootName" } } } else { tasks.register("unpackJdk${taskSuffix}", Exec) { dependsOn "downloadJdk${taskSuffix}" def jdkDetails = new JDKDetails(gradle.ext.versions.bundled_jdk, osName, archName) commandLine 'tar', 'xf', "${projectDir}/build/${jdkDetails.localPackageName}", '-C', "${projectDir}/${jdkFolderName}", '--strip-components', '1' doFirst { mkdir "${projectDir}/${jdkFolderName}" } } } } } tasks.register("copyAllJdks") { dependsOn bootstrap getTargetPlatforms().each { platform -> String taskSuffix = "${platform.os.capitalize()}${platform.arch == 'x86_64' ? 'X64' : 'Arm64'}" dependsOn "unpackJdk${taskSuffix}" } doLast { String arch = selectArch() String jdkFolder = ToolingUtils.jdkFolderName("linux", arch) File releaseFile = file("${projectDir}/${jdkFolder}/release") if (releaseFile.exists()) { def content = releaseFile.text def matcher = content =~ /IMPLEMENTOR_VERSION="([^"]+)"/ if (matcher.find()) { file("${projectDir}/JDK_VERSION").write(matcher.group(1)) } } } } tasks.register("deleteAllJdks", Delete) { delete fileTree(projectDir) { include 'jdk-*' } } tasks.register("javaTests") { dependsOn ":logstash-core:javaTests" dependsOn ":jvm-options-parser:test" } clean { dependsOn deleteAllJdks delete "${projectDir}/JDK_VERSION" delete fileTree(projectDir) { include 'output*.csv' include 'output*.json' } delete fileTree("${projectDir}/.buildkite/scripts/snyk/artifact-scan") { include 'output*.csv' include 'output*.json' } } tasks.register("extractArtifactVersions") { description = "Extract versions from a Logstash artifact (generates SBOM for Snyk scanning)" dependsOn bootstrap doLast { def artifactDir = project.findProperty('artifactDir') def outputFile = project.findProperty('outputFile') ?: 'output.csv' if (!artifactDir) { throw new GradleException("artifactDir property required. Usage: ./gradlew extractArtifactVersions -PartifactDir=/path/to/logstash -PoutputFile=output.csv") } rake(projectDir, buildDir, 'extract:artifact_versions', artifactDir, outputFile) } } tasks.register("cleanExtraction") { description = "Clean artifact extraction output files" doLast { rake(projectDir, buildDir, 'extract:clean') } } if (System.getenv('OSS') != 'true') { evaluationDependsOn(":logstash-xpack") project(":logstash-xpack") { ["rubyTests", "rubyIntegrationTests", "test"].each { tsk -> tasks.getByPath(":logstash-xpack:" + tsk).configure { dependsOn installDevelopmentGems dependsOn installDefaultGems } } } } tasks.register("runXPackUnitTests") { dependsOn copyPluginTestAlias dependsOn ":logstash-xpack:rubyTests" } tasks.register("runXPackIntegrationTests") { dependsOn copyPluginTestAlias dependsOn ":logstash-xpack:rubyIntegrationTests" }