Skip to content

Commit

Permalink
[Backport] Consolidate docker availability logic (#52656)
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-vieira authored Feb 21, 2020
1 parent 8abfda0 commit f06d692
Show file tree
Hide file tree
Showing 17 changed files with 668 additions and 542 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import static org.elasticsearch.gradle.tool.Boilerplate.maybeConfigure

plugins {
id 'lifecycle-base'
id 'elasticsearch.docker-support'
id 'elasticsearch.global-build-info'
id "com.diffplug.gradle.spotless" version "3.24.2" apply false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ import java.nio.charset.StandardCharsets
import java.nio.file.Files

import static org.elasticsearch.gradle.tool.Boilerplate.maybeConfigure
import static org.elasticsearch.gradle.tool.DockerUtils.assertDockerIsAvailable
import static org.elasticsearch.gradle.tool.DockerUtils.getDockerPath

/**
* Encapsulates build configuration for elasticsearch projects.
Expand Down Expand Up @@ -208,51 +206,6 @@ class BuildPlugin implements Plugin<Project> {
}
}

static void requireDocker(final Task task) {
final Project rootProject = task.project.rootProject
ExtraPropertiesExtension ext = rootProject.extensions.getByType(ExtraPropertiesExtension)

if (rootProject.hasProperty('requiresDocker') == false) {
/*
* This is our first time encountering a task that requires Docker. We will add an extension that will let us track the tasks
* that register as requiring Docker. We will add a delayed execution that when the task graph is ready if any such tasks are
* in the task graph, then we check two things:
* - the Docker binary is available
* - we can execute a Docker command that requires privileges
*
* If either of these fail, we fail the build.
*/

// check if the Docker binary exists and record its path
final String dockerBinary = getDockerPath().orElse(null)

final boolean buildDocker
final String buildDockerProperty = System.getProperty("build.docker")
if (buildDockerProperty == null) {
buildDocker = dockerBinary != null
} else if (buildDockerProperty == "true") {
buildDocker = true
} else if (buildDockerProperty == "false") {
buildDocker = false
} else {
throw new IllegalArgumentException(
"expected build.docker to be unset or one of \"true\" or \"false\" but was [" + buildDockerProperty + "]")
}

ext.set('buildDocker', buildDocker)
ext.set('requiresDocker', [])
rootProject.gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
final List<String> tasks = taskGraph.allTasks.intersect(ext.get('requiresDocker') as List<Task>).collect { " ${it.path}".toString()}

if (tasks.isEmpty() == false) {
assertDockerIsAvailable(task.project, tasks)
}
}
}

(ext.get('requiresDocker') as List<Task>).add(task)
}

/** Add a check before gradle execution phase which ensures java home for the given java version is set. */
static void requireJavaHome(Task task, int version) {
// use root project for global accounting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
import org.elasticsearch.gradle.ElasticsearchDistribution.Flavor;
import org.elasticsearch.gradle.ElasticsearchDistribution.Platform;
import org.elasticsearch.gradle.ElasticsearchDistribution.Type;
import org.elasticsearch.gradle.docker.DockerSupportPlugin;
import org.elasticsearch.gradle.docker.DockerSupportService;
import org.elasticsearch.gradle.info.BuildParams;
import org.elasticsearch.gradle.info.GlobalBuildInfoPlugin;
import org.elasticsearch.gradle.tool.Boilerplate;
import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Plugin;
Expand All @@ -38,6 +41,7 @@
import org.gradle.api.file.FileTree;
import org.gradle.api.file.RelativePath;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Sync;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.authentication.http.HttpHeaderAuthentication;
Expand Down Expand Up @@ -72,11 +76,17 @@ public class DistributionDownloadPlugin implements Plugin<Project> {
public void apply(Project project) {
// this is needed for isInternal
project.getRootProject().getPluginManager().apply(GlobalBuildInfoPlugin.class);
project.getRootProject().getPluginManager().apply(DockerSupportPlugin.class);

Provider<DockerSupportService> dockerSupport = Boilerplate.getBuildService(
project.getGradle().getSharedServices(),
DockerSupportPlugin.DOCKER_SUPPORT_SERVICE_NAME
);

distributionsContainer = project.container(ElasticsearchDistribution.class, name -> {
Configuration fileConfiguration = project.getConfigurations().create("es_distro_file_" + name);
Configuration extractedConfiguration = project.getConfigurations().create("es_distro_extracted_" + name);
return new ElasticsearchDistribution(name, project.getObjects(), fileConfiguration, extractedConfiguration);
return new ElasticsearchDistribution(name, project.getObjects(), dockerSupport, fileConfiguration, extractedConfiguration);
});
project.getExtensions().add(CONTAINER_NAME, distributionsContainer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

package org.elasticsearch.gradle;

import org.elasticsearch.gradle.docker.DockerSupportService;
import org.gradle.api.Buildable;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskDependency;

import java.io.File;
import java.util.Collections;
import java.util.Iterator;
import java.util.Locale;

Expand Down Expand Up @@ -110,6 +113,7 @@ public String toString() {
}

private final String name;
private final Provider<DockerSupportService> dockerSupport;
// pkg private so plugin can configure
final Configuration configuration;
private final Extracted extracted;
Expand All @@ -119,21 +123,25 @@ public String toString() {
private final Property<Platform> platform;
private final Property<Flavor> flavor;
private final Property<Boolean> bundledJdk;
private final Property<Boolean> failIfUnavailable;

ElasticsearchDistribution(
String name,
ObjectFactory objectFactory,
Provider<DockerSupportService> dockerSupport,
Configuration fileConfiguration,
Configuration extractedConfiguration
) {
this.name = name;
this.dockerSupport = dockerSupport;
this.configuration = fileConfiguration;
this.version = objectFactory.property(String.class).convention(VersionProperties.getElasticsearch());
this.type = objectFactory.property(Type.class);
this.type.convention(Type.ARCHIVE);
this.platform = objectFactory.property(Platform.class);
this.flavor = objectFactory.property(Flavor.class);
this.bundledJdk = objectFactory.property(Boolean.class);
this.failIfUnavailable = objectFactory.property(Boolean.class).convention(true);
this.extracted = new Extracted(extractedConfiguration);
}

Expand Down Expand Up @@ -182,6 +190,14 @@ public void setBundledJdk(Boolean bundledJdk) {
this.bundledJdk.set(bundledJdk);
}

public boolean getFailIfUnavailable() {
return this.failIfUnavailable.get();
}

public void setFailIfUnavailable(boolean failIfUnavailable) {
this.failIfUnavailable.set(failIfUnavailable);
}

@Override
public String toString() {
return configuration.getSingleFile().toString();
Expand All @@ -203,6 +219,13 @@ public Extracted getExtracted() {

@Override
public TaskDependency getBuildDependencies() {
// For non-required Docker distributions, skip building the distribution is Docker is unavailable
if (getType() == Type.DOCKER
&& getFailIfUnavailable() == false
&& dockerSupport.get().getDockerAvailability().isAvailable == false) {
return task -> Collections.emptySet();
}

return configuration.getBuildDependencies();
}

Expand All @@ -222,7 +245,7 @@ void finalizeValues() {
if (getType() == Type.INTEG_TEST_ZIP) {
if (platform.getOrNull() != null) {
throw new IllegalArgumentException(
"platform not allowed for elasticsearch distribution [" + name + "] of type [integ_test_zip]"
"platform cannot be set on elasticsearch distribution [" + name + "] of type [integ_test_zip]"
);
}
if (flavor.getOrNull() != null) {
Expand All @@ -232,12 +255,18 @@ void finalizeValues() {
}
if (bundledJdk.getOrNull() != null) {
throw new IllegalArgumentException(
"bundledJdk not allowed for elasticsearch distribution [" + name + "] of type [integ_test_zip]"
"bundledJdk cannot be set on elasticsearch distribution [" + name + "] of type [integ_test_zip]"
);
}
return;
}

if (getType() != Type.DOCKER && failIfUnavailable.get() == false) {
throw new IllegalArgumentException(
"failIfUnavailable cannot be 'false' on elasticsearch distribution [" + name + "] of type [" + getType() + "]"
);
}

if (getType() == Type.ARCHIVE) {
// defaults for archive, set here instead of via convention so integ-test-zip can verify they are not set
if (platform.isPresent() == false) {
Expand All @@ -246,7 +275,12 @@ void finalizeValues() {
} else { // rpm, deb or docker
if (platform.isPresent()) {
throw new IllegalArgumentException(
"platform not allowed for elasticsearch distribution [" + name + "] of type [" + getType() + "]"
"platform cannot be set on elasticsearch distribution [" + name + "] of type [" + getType() + "]"
);
}
if (getType() == Type.DOCKER && bundledJdk.isPresent()) {
throw new IllegalArgumentException(
"bundledJdk cannot be set on elasticsearch distribution [" + name + "] of type [docker]"
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.elasticsearch.gradle.docker;

import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.provider.Provider;

import java.io.File;
import java.util.List;
import java.util.stream.Collectors;

/**
* Plugin providing {@link DockerSupportService} for detecting Docker installations and determining requirements for Docker-based
* Elasticsearch build tasks.
* <p>
* Additionally registers a task graph listener used to assert a compatible Docker installation exists when task requiring Docker are
* scheduled for execution. Tasks may declare a Docker requirement via an extra property. If a compatible Docker installation is not
* available on the build system an exception will be thrown prior to task execution.
*
* <pre>
* task myDockerTask {
* ext.requiresDocker = true
* }
* </pre>
*/
public class DockerSupportPlugin implements Plugin<Project> {
public static final String DOCKER_SUPPORT_SERVICE_NAME = "dockerSupportService";
public static final String DOCKER_ON_LINUX_EXCLUSIONS_FILE = ".ci/dockerOnLinuxExclusions";
public static final String REQUIRES_DOCKER_ATTRIBUTE = "requiresDocker";

@Override
public void apply(Project project) {
if (project != project.getRootProject()) {
throw new IllegalStateException(this.getClass().getName() + " can only be applied to the root project.");
}

Provider<DockerSupportService> dockerSupportServiceProvider = project.getGradle()
.getSharedServices()
.registerIfAbsent(
DOCKER_SUPPORT_SERVICE_NAME,
DockerSupportService.class,
spec -> spec.parameters(
params -> { params.setExclusionsFile(new File(project.getRootDir(), DOCKER_ON_LINUX_EXCLUSIONS_FILE)); }
)
);

// Ensure that if any tasks declare they require docker, we assert an available Docker installation exists
project.getGradle().getTaskGraph().whenReady(graph -> {
List<String> dockerTasks = graph.getAllTasks().stream().filter(task -> {
ExtraPropertiesExtension ext = task.getExtensions().getExtraProperties();
return ext.has(REQUIRES_DOCKER_ATTRIBUTE) && (boolean) ext.get(REQUIRES_DOCKER_ATTRIBUTE);
}).map(Task::getPath).collect(Collectors.toList());

if (dockerTasks.isEmpty() == false) {
dockerSupportServiceProvider.get().failIfDockerUnavailable(dockerTasks);
}
});
}

}
Loading

0 comments on commit f06d692

Please sign in to comment.