diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPluginOfflineBranchesFallbackFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPluginOfflineBranchesFallbackFuncTest.groovy new file mode 100644 index 0000000000000..573d20fa09d5f --- /dev/null +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPluginOfflineBranchesFallbackFuncTest.groovy @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.gradle.internal.info + +import org.elasticsearch.gradle.fixtures.AbstractGradleFuncTest +import org.gradle.testkit.runner.TaskOutcome + +class GlobalBuildInfoPluginOfflineBranchesFallbackFuncTest extends AbstractGradleFuncTest { + + def setup() { + // This plugin reads files from the workspace and performs environment detection + // that is not compatible with the configuration cache in functional tests. + configurationCacheCompatible = false + } + + def "offline mode falls back to local branches.json when present"() { + given: + writeBwcVersionSource() + writeBranchesJson() + writeBuildThatResolvesBwcVersions() + + when: + def result = gradleRunner( + "resolveBwcVersions", + "--offline", + // Make any accidental outbound HTTPS attempt fail deterministically. + "-Dhttps.proxyHost=127.0.0.1", + "-Dhttps.proxyPort=1", + "-Dhttp.proxyHost=127.0.0.1", + "-Dhttp.proxyPort=1", + "-DBWC_VERSION_SOURCE=${file("Version.java").absolutePath}" + ).build() + + then: + result.task(":resolveBwcVersions").outcome == TaskOutcome.SUCCESS + assertOutputContains( + result.output, + "Gradle is running in offline mode; falling back to local branches.json" + ) + assertOutputContains(result.output, "BWC_VERSIONS_RESOLVED") + } + + def "offline mode without local branches.json retains failure behavior"() { + given: + writeBwcVersionSource() + // Intentionally do not create branches.json + writeBuildThatResolvesBwcVersions() + + when: + def result = gradleRunner( + "resolveBwcVersions", + "--offline", + "-Dhttps.proxyHost=127.0.0.1", + "-Dhttps.proxyPort=1", + "-Dhttp.proxyHost=127.0.0.1", + "-Dhttp.proxyPort=1", + "-DBWC_VERSION_SOURCE=${file("Version.java").absolutePath}" + ).buildAndFail() + + then: + assertOutputContains(result.output, "Failed to download branches.json from:") + assertOutputMissing(result.output, "falling back to local branches.json") + } + + def "non-offline mode does not fall back to local branches.json"() { + given: + writeBwcVersionSource() + writeBranchesJson() + writeBuildThatResolvesBwcVersions() + + when: + def result = gradleRunner( + "resolveBwcVersions", + // Not passing --offline on purpose. + "-Dhttps.proxyHost=127.0.0.1", + "-Dhttps.proxyPort=1", + "-Dhttp.proxyHost=127.0.0.1", + "-Dhttp.proxyPort=1", + "-DBWC_VERSION_SOURCE=${file("Version.java").absolutePath}" + ).buildAndFail() + + then: + assertOutputContains(result.output, "Failed to download branches.json from:") + assertOutputMissing(result.output, "falling back to local branches.json") + } + + private void writeBuildThatResolvesBwcVersions() { + buildFile.text = """ + plugins { + id 'elasticsearch.global-build-info' + } + + tasks.register("resolveBwcVersions") { + doLast { + // Force bwcVersions resolution, which triggers branches.json loading. + def bwcVersions = buildParams.bwcVersionsProvider.get() + println "BWC_VERSIONS_RESOLVED=[" + bwcVersions + "]" + } + } + """ + } + + private void writeBranchesJson() { + file("branches.json").text = """\ + { + "branches": [ + { "branch": "main", "version": "9.1.0" }, + { "branch": "9.0", "version": "9.0.0" } + ] + } + """.stripIndent() + } + + private void writeBwcVersionSource() { + // This file is parsed via regex; version constants must have a non-word + // character before `public` and must end with `);` to match the pattern. + file("Version.java").text = """\ +package org.elasticsearch; + +public class Version { + // Only used by GlobalBuildInfoPlugin in tests via regex parsing. + public static final Version V_9_0_0 = Version.fromId(9000000); + public static final Version V_9_1_0 = Version.fromId(9010000); + + private static Version fromId(int id) { + return new Version(); + } +} +""" + } +} + diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java index d733da2a4b690..7cb3dfaa52e0b 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java @@ -214,9 +214,24 @@ private List parseVersionLines(List versionLines) { } private List getDevelopmentBranches() { - String branchesFileLocation = project.getProviders() - .gradleProperty(BRANCHES_FILE_LOCATION_PROPERTY) - .getOrElse(DEFAULT_BRANCHES_FILE_URL); + Provider branchesFileLocationProperty = project.getProviders().gradleProperty(BRANCHES_FILE_LOCATION_PROPERTY); + boolean hasExplicitBranchesFileLocation = branchesFileLocationProperty.isPresent(); + String branchesFileLocation = hasExplicitBranchesFileLocation ? branchesFileLocationProperty.get() : DEFAULT_BRANCHES_FILE_URL; + if (hasExplicitBranchesFileLocation == false + && project.getGradle().getStartParameter().isOffline() + && branchesFileLocation.startsWith("http")) { + File localBranchesFile = new File(Util.locateElasticsearchWorkspace(project.getGradle()), "branches.json"); + if (localBranchesFile.isFile()) { + LOGGER.warn( + "Gradle is running in offline mode; falling back to local branches.json at [{}] instead of downloading from [{}]. " + + "To override, set Gradle property [{}].", + localBranchesFile.getAbsolutePath(), + branchesFileLocation, + BRANCHES_FILE_LOCATION_PROPERTY + ); + branchesFileLocation = localBranchesFile.getAbsolutePath(); + } + } LOGGER.info("Reading branches.json from {}", branchesFileLocation); byte[] branchesBytes; if (branchesFileLocation.startsWith("http")) { diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/EmptyDirTaskTests.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/EmptyDirTaskTests.java index 083804d84b405..2972234322c9b 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/EmptyDirTaskTests.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/EmptyDirTaskTests.java @@ -15,6 +15,10 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Collections; +import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -61,9 +65,8 @@ public void testCreateEmptyDirNoPermissions() throws Exception { assertTrue(newEmptyFolder.exists()); assertTrue(newEmptyFolder.isDirectory()); - assertFalse(newEmptyFolder.canExecute()); - assertFalse(newEmptyFolder.canRead()); - assertFalse(newEmptyFolder.canWrite()); + Set permissions = Files.getPosixFilePermissions(newEmptyFolder.toPath()); + assertEquals(Collections.emptySet(), permissions); // cleanup newEmptyFolder.delete();