com.azurenight.maven.TroposphereMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of troposphere-maven-plugin Show documentation
Show all versions of troposphere-maven-plugin Show documentation
This plugin makes use of Jython and 2 Pthyon modules (Boto and Troposphere) to allow for automated generation of AWS CloudFormation templates from a Python source file. This alleviates
the need to maintain a large and possibly cumbersome JSON file by hand, and allows for proper commenting, modularization, etc.
package com.azurenight.maven;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed 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.
*/
/*
* Much code originally from jython-compile-maven-plugin project @
* http://sourceforge.net/p/mavenjython/
* Original author Johannes Buchner
*
* Modified by Eduard Martinescu
*/
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
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 org.codehaus.plexus.util.DirectoryScanner;
@Mojo(name = "tropo", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
public class TroposphereMojo extends AbstractMojo {
private static final String SETUPTOOLS_EGG = "setuptools-0.6c11-py2.7.egg";
private static final String BOTO_EGG = "boto-2.9.4-py2.7.egg";
private static final String TROPO_EGG = "troposphere-0.3.4-py2.7.egg";
private Artifact jythonArtifact;
/**
* Caching directory to download and build python packages, as well as
* extracted jython dir
*
*/
@Parameter(defaultValue = "target/troposphere-build-tmp", property = "tempDirectory")
private File temporaryBuildDirectory;
/**
* The directory where the Python files (*.tr
) are
* located. Use of the '.tr' suffix instead of the customary '.py'
* is intentional to make it easy to include modules/other python code
* that is not converted by default.
* All output generated by the '.tr' files in question are captured
* and created as a cloud template with the same basename
*/
@Parameter(defaultValue = "${basedir}/src/main/troposphere", property = "sourceDirectory")
private File sourceDirectory;
/**
* The directory generated AWS cloud template files are stored.
* The directory will be registered as a
* compile source root of the project such that the generated files will
* participate in later build phases like
* compiling and packaging.
*
*/
@Parameter(defaultValue = "${basedir}/src/cloud-templates", property = "outputDirectory")
private File outputDirectory;
/**
* The granularity in milliseconds of the last modification date for testing
* whether a source needs recompilation.
*
*/
@Parameter(property = "lastModGranularityMs", defaultValue = "0")
private int staleMillis;
/**
* A set of Ant-like inclusion patterns used to select files from the source
* directory for processing. By default,
* the patterns **/*.tr
and **/*.TR
are used
* to select troposphere files.
*
*/
@Parameter
private String[] includes;
/**
* A set of Ant-like exclusion patterns used to prevent certain files from
* being processed. By default, this set is
* empty such that no files are excluded.
*
*/
@Parameter
private String[] excludes;
/**
* List of other Python modules that need to be installed prior to processing of your '.tr' files
* Installation via easy install/setup tools
*
*/
@Parameter(property="libs")
private List libs;
@Component
private MavenProject project;
/**
* The setuptools jar resource
*/
private URL setuptoolsResource;
/**
* The setuptools jar, once copied from the resource
*/
private File setuptoolsJar;
/**
* URL & File to copy boto egg from within plugin
*/
private URL botoURL;
private File botoFile;
/**
* URL & File to copy troposphere egg
*/
private URL tropoURL;
private File tropoFile;
/**
* Lib/site-packages
*/
private File sitepackagesdir;
/**
* Where packages are downloaded and built
*/
private File packageDownloadCacheDir;
/**
* Lib/
*/
private File libdir;
/**
* Should we override files during extraction if they already exist?
*
* if true: will never work on tainted files; if false: will be faster.
*/
private static final boolean OVERRIDE = false;
protected String[] getIncludes() {
if (this.includes != null) {
return this.includes;
}
else {
return new String[] { "**/*.tr", "**/*.TR" };
}
}
protected String[] getExcludes() {
return this.excludes;
}
protected File getOutputDirectory() {
return this.outputDirectory;
}
protected int getStaleMillis() {
return this.staleMillis;
}
protected File getSourceDirectory() {
return this.sourceDirectory;
}
protected List getLibraries() {
return this.libs;
}
public void execute() throws MojoExecutionException {
File sourceDirectory = getSourceDirectory();
getLog().debug("source=" + sourceDirectory + " target=" + getOutputDirectory());
if (!(sourceDirectory != null && sourceDirectory.exists())) {
getLog().info("Request to add '" + sourceDirectory + "' folder. Not added since it does not exist.");
return;
}
File f = outputDirectory;
if (!f.exists()) {
f.mkdirs();
}
setupVariables();
extractJarToDirectory(jythonArtifact.getFile(), temporaryBuildDirectory);
// now what? we have the jython content, now we need
// easy_install
getLog().debug("installing easy_install ...");
try {
FileUtils.copyInputStreamToFile(setuptoolsResource.openStream(), setuptoolsJar);
}
catch (IOException e) {
throw new MojoExecutionException("copying setuptools failed", e);
}
try {
FileUtils.copyInputStreamToFile(botoURL.openStream(), botoFile);
}
catch (IOException e) {
throw new MojoExecutionException("copying boto failed", e);
}
try {
FileUtils.copyInputStreamToFile(tropoURL.openStream(), tropoFile);
}
catch (IOException e) {
throw new MojoExecutionException("copying troposphere failed", e);
}
extractJarToDirectory(setuptoolsJar, new File(sitepackagesdir, SETUPTOOLS_EGG));
try {
IOUtils.write("./" + SETUPTOOLS_EGG + "\n", new FileOutputStream(new File(sitepackagesdir, "setuptools.pth")));
}
catch (IOException e) {
throw new MojoExecutionException("writing path entry for setuptools failed", e);
}
getLog().debug("installing easy_install done");
if (libs == null) {
getLog().info("no python libraries requested");
}
else {
getLog().debug("installing requested python libraries");
// then we need to call easy_install to install the other
// dependencies.
runJythonScriptOnInstall(temporaryBuildDirectory, getEasyInstallArgs("Lib/site-packages/" + SETUPTOOLS_EGG + "/easy_install.py"), null);
getLog().debug("installing requested python libraries done");
}
processFiles();
}
/**
* @throws MojoExecutionException
*
*/
private void processFiles() throws MojoExecutionException {
DirectoryScanner ds = new DirectoryScanner();
ds.setBasedir(getSourceDirectory());
ds.setIncludes(getIncludes());
ds.setExcludes(getExcludes());
ds.addDefaultExcludes();
ds.scan();
String[] files = ds.getIncludedFiles();
for (String file : files) {
getLog().info("Processing file: " + file);
File fullFile = new File(sourceDirectory, file);
String destFile = file;
if (FilenameUtils.indexOfExtension(file) > -1) {
destFile = file.substring(0, FilenameUtils.indexOfExtension(file)) + ".template";
}
runJythonScriptOnInstall(temporaryBuildDirectory, getPythonArgs(fullFile.getAbsolutePath()), new File(outputDirectory, destFile));
}
}
/**
* @param file
* @return
* @throws MojoExecutionException
*/
private List getPythonArgs(String file) throws MojoExecutionException {
List args = new ArrayList();
// I want to launch
args.add("java");
// to run the generated jython installation here
args.add("-cp");
args.add("." + getClassPathSeparator() + "Lib");
// which should know about itself
args.add("-Dpython.home=.");
File jythonFakeExecutable = new File(temporaryBuildDirectory, "jython");
try {
jythonFakeExecutable.createNewFile();
}
catch (IOException e) {
throw new MojoExecutionException("couldn't create file", e);
}
args.add("-Dpython.executable=" + jythonFakeExecutable.getName());
args.add("org.python.util.jython");
// and it should run the supplied file
args.add(file);
return args;
}
private void setupVariables() throws MojoExecutionException {
jythonArtifact = findJythonArtifact();
if (temporaryBuildDirectory == null) {
temporaryBuildDirectory = new File("target/jython-plugins-tmp");
}
temporaryBuildDirectory.mkdirs();
packageDownloadCacheDir = new File(temporaryBuildDirectory, "build");
packageDownloadCacheDir.mkdir();
libdir = new File(temporaryBuildDirectory, "Lib");
if (!jythonArtifact.getFile().getName().endsWith(".jar")) {
throw new MojoExecutionException("I expected " + jythonArtifact + " to provide a jar, but got " + jythonArtifact.getFile());
}
setuptoolsResource = getClass().getResource(SETUPTOOLS_EGG);
if (setuptoolsResource == null)
throw new MojoExecutionException("resource setuptools egg not found");
setuptoolsJar = new File(packageDownloadCacheDir, SETUPTOOLS_EGG);
sitepackagesdir = new File(libdir, "site-packages");
botoURL = getClass().getResource(BOTO_EGG);
botoFile = new File(packageDownloadCacheDir,BOTO_EGG);
tropoURL = getClass().getResource(TROPO_EGG);
tropoFile = new File(packageDownloadCacheDir,TROPO_EGG);
if (libs == null) {
getLog().info("libraries list empty");
libs = new ArrayList();
}
if (!libs.contains("boto")) {
getLog().debug("missing boto library, adding automatically");
libs.add(botoFile.getAbsolutePath());
}
if (!libs.contains("troposphere")) {
getLog().debug("missing troposphere library, adding automatically");
libs.add(tropoFile.getAbsolutePath());
}
}
/**
* @return
* @throws MojoExecutionException
*/
private Artifact findJythonArtifact() throws MojoExecutionException {
for (Artifact i : project.getArtifacts()) {
if (i.getArtifactId().equals("jython-standalone") && i.getGroupId().equals("org.python")) {
return i;
}
}
throw new MojoExecutionException("org.python.jython-standalone dependency not found. \n" + "Add a dependency to jython-standalone 2.7-b1 or newer to your project: \n" + " \n" + " org.python \n" + " jython-standalone \n" + " 2.7-b1 \n" + " " + "\n");
}
public Collection extractJarToDirectory(File jar, File outputDirectory) throws MojoExecutionException {
getLog().debug("extracting " + jar);
JarFile ja = openJarFile(jar);
Enumeration en = ja.entries();
Collection files = extractAllFiles(outputDirectory, ja, en);
closeFile(ja);
return files;
}
private JarFile openJarFile(File jar) throws MojoExecutionException {
try {
return new JarFile(jar);
}
catch (IOException e) {
throw new MojoExecutionException("opening jython artifact jar failed", e);
}
}
private void closeFile(ZipFile ja) throws MojoExecutionException {
try {
ja.close();
}
catch (IOException e) {
throw new MojoExecutionException("closing jython artifact jar failed", e);
}
}
private Collection extractAllFiles(File outputDirectory, ZipFile ja, Enumeration en) throws MojoExecutionException {
List files = new ArrayList();
while (en.hasMoreElements()) {
JarEntry el = en.nextElement();
if (!el.isDirectory()) {
File destFile = new File(outputDirectory, el.getName());
if (OVERRIDE || !destFile.exists()) {
destFile.getParentFile().mkdirs();
try {
FileOutputStream fo = new FileOutputStream(destFile);
IOUtils.copy(ja.getInputStream(el), fo);
fo.close();
}
catch (IOException e) {
throw new MojoExecutionException("extracting " + el.getName() + " from jython artifact jar failed", e);
}
}
files.add(destFile);
}
}
return files;
}
private List getEasyInstallArgs(String easy_install_script) throws MojoExecutionException {
List args = new ArrayList();
// I want to launch
args.add("java");
// to run the generated jython installation here
args.add("-cp");
args.add("." + getClassPathSeparator() + "Lib");
// which should know about itself
args.add("-Dpython.home=.");
//args.add("-Dpython.verbose=debug");
File jythonFakeExecutable = new File(temporaryBuildDirectory, "jython");
try {
jythonFakeExecutable.createNewFile();
}
catch (IOException e) {
throw new MojoExecutionException("couldn't create file", e);
}
args.add("-Dpython.executable=" + jythonFakeExecutable.getName());
args.add("org.python.util.jython");
args.add(easy_install_script);
args.add("--always-unzip");
args.add("--upgrade");
args.add("--verbose");
args.add("--build-directory");
args.add(packageDownloadCacheDir.getAbsolutePath());
// and install these libraries
args.addAll(libs);
return args;
}
private String getClassPathSeparator() {
if (File.separatorChar == '\\')
return ";";
else
return ":";
}
public void runJythonScriptOnInstall(File outputDirectory, List args, File outputFile) throws MojoExecutionException {
getLog().info("running " + args + " in " + outputDirectory);
ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(outputDirectory);
pb.environment().put("BASEDIR", project.getBasedir().getAbsolutePath());
final Process p;
ByteArrayOutputStream stdoutBaos = null;
ByteArrayOutputStream stderrBaos = null;
try {
p = pb.start();
}
catch (IOException e) {
throw new MojoExecutionException("Executing jython failed. tried to run: " + pb.command(), e);
}
if (outputFile == null) {
stdoutBaos = new ByteArrayOutputStream();
copyIO(p.getInputStream(), stdoutBaos);
}
else {
try {
copyIO(p.getInputStream(), new FileOutputStream(outputFile));
}
catch (FileNotFoundException e) {
throw new MojoExecutionException("Failed to copy output to : " + outputFile.getAbsolutePath(), e);
}
}
stderrBaos = new ByteArrayOutputStream();
copyIO(p.getErrorStream(), stderrBaos);
copyIO(System.in, p.getOutputStream());
try {
boolean error = false;
if (p.waitFor() != 0) {
error = true;
}
if (getLog().isDebugEnabled() && stdoutBaos != null) {
getLog().debug(stdoutBaos.toString());
}
if (getLog().isErrorEnabled() && stderrBaos != null) {
getLog().error(stderrBaos.toString());
}
if (error) {
throw new MojoExecutionException("Jython failed with return code: " + p.exitValue());
}
}
catch (InterruptedException e) {
throw new MojoExecutionException("Python tests were interrupted", e);
}
}
private void copyIO(final InputStream input, final OutputStream output) {
new Thread(new Runnable() {
public void run() {
try {
IOUtils.copy(input, output);
}
catch (IOException e) {
getLog().error(e);
}
}
}).start();
}
}