Skip to content

Commit

Permalink
Merge pull request #94 from nvervelle/issue-41-option-to-download-opa
Browse files Browse the repository at this point in the history
Add an option to download OPA during gradle build
  • Loading branch information
radrys authored Sep 21, 2023
2 parents 1ae5f11 + 672bfa0 commit ba2bd9a
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 4 deletions.
47 changes: 47 additions & 0 deletions src/main/java/com/bisnode/opa/DownloadOpaTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.bisnode.opa;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

import com.bisnode.opa.configuration.OpaPlatform;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

public abstract class DownloadOpaTask extends DefaultTask {

public static final String TASK_BASE_NAME = "downloadOpa";

@Input
public abstract Property<String> getVersion();

@OutputFile
public abstract RegularFileProperty getOutputFile();

public DownloadOpaTask() {
setGroup("opa");
setDescription("Download OPA");
}

@TaskAction
public void downloadOpa() throws IOException {
final String downloadUrl = OpaPlatform.getPlatform().getDownloadUrl(getVersion().get());
getLogger().info("Retrieving OPA executable from " + downloadUrl);
final File targetFile = getOutputFile().getAsFile().get();
getLogger().info("Saving OPA executable to " + targetFile.getAbsolutePath());
try (FileOutputStream output = new FileOutputStream(targetFile);
ReadableByteChannel input = Channels.newChannel(new URL(downloadUrl).openStream())) {
output.getChannel().transferFrom(input, 0, Long.MAX_VALUE);
}
if (!targetFile.setReadable(true) || !targetFile.setExecutable(true)) {
throw new IllegalStateException("Unable to set permissions on OPA executable");
}
}
}
67 changes: 63 additions & 4 deletions src/main/java/com/bisnode/opa/OpaPlugin.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package com.bisnode.opa;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.bisnode.opa.configuration.DefaultOpaConfiguration;
import com.bisnode.opa.configuration.DefaultOpaPluginConvention;
import com.bisnode.opa.configuration.ExecutableMode;
import com.bisnode.opa.configuration.OpaConfiguration;
import com.bisnode.opa.configuration.OpaPlatform;
import com.bisnode.opa.configuration.OpaPluginConvention;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;

@SuppressWarnings("unused")
public class OpaPlugin implements Plugin<Project> {
Expand All @@ -18,10 +28,59 @@ public void apply(Project project) {
project.getExtensions().create(OpaConfiguration.class, "opa", DefaultOpaConfiguration.class, convention);

TaskContainer tasks = project.getTasks();
tasks.create("startOpa", StartOpaTask.class);
tasks.create("stopOpa", StopOpaTask.class);
tasks.create("testRego", TestRegoTask.class);
tasks.create("testRegoCoverage", TestRegoCoverageTask.class);
List<Task> addedTasks = new ArrayList<>();
addedTasks.add(tasks.create("startOpa", StartOpaTask.class));
addedTasks.add(tasks.create("stopOpa", StopOpaTask.class));
addedTasks.add(tasks.create("testRego", TestRegoTask.class));
addedTasks.add(tasks.create("testRegoCoverage", TestRegoCoverageTask.class));

project.afterEvaluate(currentProject -> applyToRootProject(currentProject, addedTasks));
}

private void applyToRootProject(Project project, List<Task> dependentTasks) {
OpaConfiguration opaConfiguration = project.getExtensions().findByType(OpaConfiguration.class);
if (opaConfiguration == null) {
return;
}
if (!ExecutableMode.DOWNLOAD.equals(opaConfiguration.getMode())) {
return;
}
String version = opaConfiguration.getVersion();
if (version == null || version.trim().isEmpty()) {
throw new IllegalStateException("You must specify OPA version in DOWNLOAD mode");
}

// When the current plugin is executed in a subproject,
// the root project may (or may not) have been executed.
// We need different strategies to apply depending on root project state
Project rootProject = project.getRootProject();
if (rootProject.getState().getExecuted()) {
applyDownloadTask(rootProject, version, dependentTasks);
} else {
rootProject.afterEvaluate(root -> applyDownloadTask(root, version, dependentTasks));
}
}

private synchronized void applyDownloadTask(Project project, String version, List<Task> dependentTasks) {
final String taskName = String.format("%s_%s", DownloadOpaTask.TASK_BASE_NAME, version);
final File opaExecutable = OpaPlatform.getPlatform().getExecutablePath(project.getBuildDir().toPath(), version).toFile();
Set<Task> downloadTasks = project.getTasksByName(taskName, false);
if (downloadTasks.isEmpty()) {
final TaskProvider<DownloadOpaTask> downloadTask = project.getTasks().register(taskName, DownloadOpaTask.class);
downloadTask.configure(task -> {
task.getVersion().set(version);
task.getOutputFile().set(opaExecutable);
});
downloadTasks = new HashSet<>();
downloadTasks.add(downloadTask.get());
}
downloadTasks.forEach(downloadTask -> dependentTasks.forEach(task -> {
task.dependsOn(downloadTask);
final OpaConfiguration opaConfiguration = task.getExtensions().findByType(OpaConfiguration.class);
if (opaConfiguration != null) {
opaConfiguration.setLocation(opaExecutable.getParent());
}
}));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bisnode.opa.configuration;

import javax.annotation.Nullable;

public class DefaultOpaConfiguration implements OpaConfiguration {

private final OpaPluginConvention opaPluginConvention;
Expand All @@ -8,6 +10,16 @@ public DefaultOpaConfiguration(OpaPluginConvention opaPluginConvention) {
this.opaPluginConvention = opaPluginConvention;
}

@Override
public ExecutableMode getMode() {
return opaPluginConvention.getMode();
}

@Override
public void setMode(ExecutableMode mode) {
opaPluginConvention.setMode(mode);
}

@Override
public String getLocation() {
return opaPluginConvention.getLocation();
Expand All @@ -18,6 +30,16 @@ public void setLocation(String location) {
opaPluginConvention.setLocation(location);
}

@Override
public @Nullable String getVersion() {
return opaPluginConvention.getVersion();
}

@Override
public void setVersion(String version) {
opaPluginConvention.setVersion(version);
}

@Override
public String getSrcDir() {
return opaPluginConvention.getSrcDir();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bisnode.opa.configuration;

import javax.annotation.Nullable;

import org.gradle.api.Project;
import org.gradle.api.reflect.HasPublicType;
import org.gradle.api.reflect.TypeOf;
Expand All @@ -8,14 +10,26 @@ public class DefaultOpaPluginConvention extends OpaPluginConvention implements H

private final Project project;

private ExecutableMode mode = ExecutableMode.LOCAL;
private String location = "opa";
@Nullable private String version;
private String srcDir = "src/main/rego";
private String testDir = "src/test/rego";

public DefaultOpaPluginConvention(Project project) {
this.project = project;
}

@Override
public ExecutableMode getMode() {
return mode;
}

@Override
public void setMode(ExecutableMode mode) {
this.mode = mode;
}

@Override
public String getLocation() {
return location;
Expand All @@ -26,6 +40,16 @@ public void setLocation(String location) {
this.location = location;
}

@Override
public @Nullable String getVersion() {
return version;
}

@Override
public void setVersion(@Nullable String version) {
this.version = version;
}

@Override
public String getSrcDir() {
return srcDir;
Expand Down Expand Up @@ -55,6 +79,7 @@ public TypeOf<?> getPublicType() {
public String toString() {
return "DefaultOpaPluginConvention{" +
"project=" + project +
", mode='" + mode + '\'' +
", location='" + location + '\'' +
", srcDir='" + srcDir + '\'' +
", testDir='" + testDir + '\'' +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.bisnode.opa.configuration;

public enum ExecutableMode {
LOCAL, DOWNLOAD
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package com.bisnode.opa.configuration;

import javax.annotation.Nullable;

@SuppressWarnings("unused")
public interface OpaConfiguration {

ExecutableMode getMode();
void setMode(ExecutableMode mode);

String getLocation();
void setLocation(String location);

@Nullable String getVersion();
void setVersion(String version);

String getSrcDir();
void setSrcDir(String srcDir);

Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/bisnode/opa/configuration/OpaPlatform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.bisnode.opa.configuration;

import java.nio.file.Path;

public enum OpaPlatform {

MAC_OS_AMD64("darwin_amd64", ""),
LINUX_AMD64("linux_amd64_static", ""),
WINDOWS_AMD64("windows_amd64", ".exe");

private final String platformQualifier;
private final String executableExtension;

OpaPlatform(final String platformQualifier, final String executableExtension) {
this.platformQualifier = platformQualifier;
this.executableExtension = executableExtension;
}

public String getDownloadUrl(final String opaVersion) {
return String.format("https://openpolicyagent.org/downloads/v%s/opa_%s%s",
opaVersion,
platformQualifier,
executableExtension);
}

public Path getExecutablePath(final Path rootPath, final String version) {
return rootPath.resolve("opa").resolve(version).resolve(String.format("opa%s", executableExtension));
}

public static OpaPlatform getPlatform() {
final String osName = System.getProperty("os.name");
final String osArch = System.getProperty("os.arch");
if (osName.contains("win")) {
if (osArch.equals("amd64")) {
return WINDOWS_AMD64;
}
} else if (osName.contains("Mac")) {
if (osArch.equals("x86_64")) {
return MAC_OS_AMD64;
}
} else if (osName.contains("Linux")) {
if (osArch.equals("amd64")) {
return LINUX_AMD64;
}
}
throw new IllegalStateException(String.format("Unsupported combination of OS/arch: %s/%s",
osName,
osArch));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.bisnode.opa.configuration;

import javax.annotation.Nullable;

import com.bisnode.opa.OpaPlugin;
import org.gradle.api.plugins.Convention;

Expand All @@ -8,10 +10,18 @@
*/
public abstract class OpaPluginConvention {

public abstract ExecutableMode getMode();

public abstract void setMode(ExecutableMode mode);

public abstract String getLocation();

public abstract void setLocation(String location);

@Nullable public abstract String getVersion();

public abstract void setVersion(String version);

public abstract String getSrcDir();

public abstract void setSrcDir(String srcDir);
Expand Down
Loading

0 comments on commit ba2bd9a

Please sign in to comment.