All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.azurenight.maven.TroposphereMojo Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 2.6.0.1
Show newest version
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();

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy