com.atlassian.bamboo.specs.maven.RunBambooSpecsMojo Maven / Gradle / Ivy
package com.atlassian.bamboo.specs.maven;
import com.atlassian.bamboo.specs.api.rsbs.RepositoryStoredSpecsData;
import com.atlassian.bamboo.specs.api.rsbs.RunnerSettings;
import com.atlassian.bamboo.specs.maven.sandbox.SpecsRunner;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.UserPrincipal;
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Mojo(
name = "run",
defaultPhase = LifecyclePhase.NONE,
requiresDependencyResolution = ResolutionScope.RUNTIME,
requiresOnline = true
)
@Execute(phase = LifecyclePhase.PACKAGE)
public class RunBambooSpecsMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", required = true, readonly = true)
private MavenProject project;
@Parameter(defaultValue = "${project.build.outputDirectory}", property = "specsDir", required = true)
private File outputDirectory;
@Parameter(defaultValue = "false")
private boolean skip;
@Parameter(property = "specs.yamlDir")
private File yamlDir;
@Parameter(property = "specs.useRest", defaultValue = "true")
private boolean useRest;
@Parameter(property = "specs.useSecurityManager", defaultValue = "false")
private boolean useSecurityManager;
//Repository-stored Bamboo Specs
@Parameter(property = "specs.rs.specsSourceId")
private Long specsSourceId;
@Override
public void execute() throws MojoExecutionException {
if (skip) {
getLog().debug("Skipping plan execution as the 'skip' property is set to true");
return;
}
getLog().info("Scanning " + outputDirectory + " for classes annotated with Bamboo plan annotation.");
final URLClassLoader classLoader = createClassLoader();
final Collection classFiles = getClassFiles(outputDirectory.toPath());
final SpecsRunner specsRunner = new SpecsRunner(getLog(), classFiles, classLoader, getPriorityClasspath());
RunnerSettings.setRestEnabled(useRest);
if (yamlDir!=null) {
RunnerSettings.setYamlDir(yamlDir.toPath());
}
if (specsSourceId !=null) {
RunnerSettings.setRepositoryStoredSpecsData(new RepositoryStoredSpecsData(specsSourceId));
}
specsRunner.runSpecs(useSecurityManager);
if (yamlDir != null) {
syncOwnerOfYamlFiles(yamlDir.toPath());
}
}
private Collection getClassFiles(final Path root) throws MojoExecutionException {
final Collection classFiles;
try {
classFiles = BambooSpecsFileScanner.getClassFiles(getLog(), root);
} catch (final IOException e) {
throw new MojoExecutionException("Unable to find class files in " + outputDirectory, e);
}
return classFiles;
}
private URLClassLoader createClassLoader() throws MojoExecutionException {
final URLClassLoader classLoader;
try {
final Stream priorityClasspath = getPriorityClasspath().stream()
.map(File::getPath);
final URL[] runtimeClasspath = Stream.concat(
priorityClasspath, project.getRuntimeClasspathElements().stream()
).map(this::newUrl)
.toArray(URL[]::new);
classLoader = new URLClassLoader(runtimeClasspath, Thread.currentThread().getContextClassLoader());
} catch (final DependencyResolutionRequiredException e) {
throw new MojoExecutionException("Unable to find dependency", e);
}
return classLoader;
}
private Set getPriorityClasspath() {
final Artifact specsApi = project.getArtifactMap().get("com.atlassian.bamboo:bamboo-specs-api");
final Artifact specs = project.getArtifactMap().get("com.atlassian.bamboo:bamboo-specs");
final Artifact snakeYaml = project.getArtifactMap().get("org.yaml:snakeyaml");
return Stream.of(specsApi, specs, snakeYaml)
.map(Artifact::getFile)
.collect(Collectors.toSet());
}
private URL newUrl(final String file) {
try {
return new File(file).toURI().toURL();
} catch (final MalformedURLException e) {
throw new RuntimeException("Unable to construct classloader", e);
}
}
/**
* Update YAML files owner to match owner of YAML output directory.
* It is done outside the part of the mojo run with security manager because we attempt to copy owner from the potentially created YAML output directory.
* This is done so when mojo is run within Docker container it will produce files that Bamboo server can later remove.
*/
private void syncOwnerOfYamlFiles(final Path yamlDirectory) throws MojoExecutionException {
try {
final UserPrincipal yamlDirectoryOwner = Files.getFileAttributeView(yamlDirectory, FileOwnerAttributeView.class).getOwner();
getLog().info("Updating ownership of files in " + yamlDirectory + " to " + yamlDirectoryOwner);
try (Stream walker = Files.walk(yamlDirectory)) {
walker.filter(path -> !Objects.equals(yamlDirectory, path))
.forEach(updateOwner(yamlDirectoryOwner));
}
} catch (IOException e) {
throw new MojoExecutionException("Unable to read YAML file owner", e);
}
}
private Consumer updateOwner(final UserPrincipal owner) {
return path -> {
try {
Files.getFileAttributeView(path, FileOwnerAttributeView.class).setOwner(owner);
getLog().debug("Updating owner of" + path);
} catch (IOException e) {
throw new RuntimeException("Unable to update YAML file owner", e);
}
};
}
}