com.urbancode.terraform.main.Main.java Source code

Java tutorial

Introduction

Here is the source code for com.urbancode.terraform.main.Main.java

Source

/*******************************************************************************
 * Copyright 2012 Urbancode, Inc
 *
 * 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.
 ******************************************************************************/
package com.urbancode.terraform.main;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.UUID;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import com.urbancode.terraform.commands.vmware.ResumeCommand;
import com.urbancode.terraform.commands.vmware.SuspendCommand;
import com.urbancode.terraform.commands.vmware.TakeSnapshotCommand;
import com.urbancode.terraform.credentials.aws.CredentialsAWS;
import com.urbancode.terraform.credentials.aws.CredentialsParserAWS;
import com.urbancode.terraform.credentials.common.Credentials;
import com.urbancode.terraform.credentials.common.CredentialsException;
import com.urbancode.terraform.credentials.common.CredentialsParser;
import com.urbancode.terraform.credentials.common.CredentialsParserRegistry;
import com.urbancode.terraform.credentials.microsoft.CredentialsMicrosoft;
import com.urbancode.terraform.credentials.microsoft.CredentialsParserMicrosoft;
import com.urbancode.terraform.credentials.rackspace.CredentialsParserRackspace;
import com.urbancode.terraform.credentials.rackspace.CredentialsRackspace;
import com.urbancode.terraform.credentials.vcloud.CredentialsParserVCloud;
import com.urbancode.terraform.credentials.vcloud.CredentialsVCloud;
import com.urbancode.terraform.credentials.vmware.CredentialsParserVmware;
import com.urbancode.terraform.credentials.vmware.CredentialsVmware;
import com.urbancode.terraform.tasks.aws.ContextAWS;
import com.urbancode.terraform.tasks.common.TerraformContext;
import com.urbancode.terraform.tasks.vmware.ContextVmware;
import com.urbancode.x2o.tasks.CreationException;
import com.urbancode.x2o.tasks.DestructionException;
import com.urbancode.x2o.tasks.RestorationException;
import com.urbancode.x2o.util.Property;
import com.urbancode.x2o.util.PropertyResolver;
import com.urbancode.x2o.xml.XmlModelParser;
import com.urbancode.x2o.xml.XmlParsingException;
import com.urbancode.x2o.xml.XmlWrite;

public class Main {

    //**********************************************************************************************
    // CLASS
    //**********************************************************************************************
    static private final Logger log = Logger.getLogger(Main.class);

    //----------------------------------------------------------------------------------------------
    /**
     * This method initializes Terraform from the command line.
     * The static main method verifies the command line arguments and terminates if they are incorrect.
     * @param args
     * @throws IOException
     * @throws XmlParsingException
     * @throws CredentialsException
     * @throws RestorationException
     * @throws DestructionException
     * @throws CreationException
     */
    static public void main(String[] args) throws IOException, XmlParsingException, CredentialsException,
            CreationException, DestructionException, RestorationException {
        File inputXmlFile = null;
        File creds = null;
        List<String> unparsedArgs = new ArrayList<String>();
        String command = null;

        if (args != null && args.length >= 3) {
            if (!AllowedCommands.contains(args[0])) {
                String msg = "Invalid first argument: " + args[0];
                log.fatal(msg);
                throw new IOException(msg);
            } else {
                command = args[0].toLowerCase();
            }

            inputXmlFile = createFile(args[1]);
            creds = createFile(args[2]);

            Collections.addAll(unparsedArgs, args);
            // make args just the unparsed properties
            unparsedArgs.remove(0); // remove inputxmlpath
            unparsedArgs.remove(0); // remove create/destroy
            unparsedArgs.remove(0); // remove creds
        } else {
            log.fatal("Invalid number of arguments!\n" + "Found " + args.length + "\n" + "Expected at least 3");
            throw new IOException("improper args");
        }

        // check to make sure we have legit args
        if (inputXmlFile == null) {
            String msg = "No input xml file specified!";
            throw new IOException(msg);
        }
        if (creds == null) {
            String msg = "No credentials file specified!";
            throw new IOException(msg);
        }

        Main myMain = new Main(command, inputXmlFile, creds, unparsedArgs);
        myMain.execute();
    }

    //----------------------------------------------------------------------------------------------
    /**
     * Creates a file and runs checks on it
     *
     * @param filePath
     * @return
     * @throws FileNotFoundException
     */
    static private File createFile(String filePath) throws FileNotFoundException {
        File result = null;

        if (!"".equals(filePath)) {
            result = new File(filePath);
            if (result.exists()) {
                if (result.isFile()) {
                    if (!result.canRead()) {
                        String msg = "Input file does not exist: " + filePath;
                        log.fatal(msg);
                        throw new FileNotFoundException(msg);
                    }
                } else {
                    String msg = "Input file is not a file: " + filePath;
                    log.fatal(msg);
                    throw new FileNotFoundException(msg);
                }
            } else {
                String msg = "Input file does not exist: " + filePath;
                log.fatal(msg);
                throw new FileNotFoundException(msg);
            }
        }

        return result;
    }

    //**********************************************************************************************
    // INSTANCE
    //**********************************************************************************************
    private String command;
    private File inputXmlFile;
    private File outputXmlFile;
    private File credsFile;
    private List<Property> props;

    //----------------------------------------------------------------------------------------------
    private Main(String command, File inputXmlFile, File credsFile, List<String> unparsed) {
        this.command = command;
        startCredsParser();
        this.credsFile = credsFile;
        this.inputXmlFile = inputXmlFile;
        props = new ArrayList<Property>();
        if (unparsed != null) {
            for (String prop : unparsed) {
                try {
                    props.add(parseProperty(prop));
                } catch (IOException e) {
                    log.error("Unable to parse property: " + prop);
                }
            }
        }
    }

    //----------------------------------------------------------------------------------------------
    /**
     * Initializes Terraform so it can execute the given commands.
     * Here is the order of operations:
     * Parses the credentials file and verifies the given credentials.
     * Generates a random string for this environment, which is appended to the output xml file.
     * Parses the xml file.
     * Runs the specified command (create, destroy, etc).
     * @throws XmlParsingException
     * @throws IOException
     * @throws CredentialsException
     * @throws CreationException
     * @throws DestructionException
     * @throws RestorationException
     */
    public void execute() throws XmlParsingException, IOException, CredentialsException, CreationException,
            DestructionException, RestorationException {
        TerraformContext context = null;
        try {
            // parse xml and set context
            context = parseContext(inputXmlFile);

            Credentials credentials = parseCredentials(credsFile);

            context.setCredentials(credentials);
            if (AllowedCommands.CREATE.getCommandName().equalsIgnoreCase(command)) {
                // create new file if creating a new environment
                UUID uuid = UUID.randomUUID();
                //convert uuid to base 62 (allowed chars: 0-9 a-z A-Z)
                ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
                bb.putLong(uuid.getMostSignificantBits());
                bb.putLong(uuid.getLeastSignificantBits());
                String suffix = Base64.encodeBase64URLSafeString(bb.array());
                suffix = suffix.replaceAll("-", "Y");
                suffix = suffix.replaceAll("_", "Z");
                suffix = suffix.substring(0, 4);
                if (context.getEnvironment() != null) {
                    context.getEnvironment().addSuffixToEnvName(suffix);
                    log.debug("UUID for env " + context.getEnvironment().getName() + " is " + suffix);
                } else {
                    throw new NullPointerException("No environment on context!");
                }

                String name = context.getEnvironment().getName();
                log.debug("Output filename = " + name);
                outputXmlFile = new File("env-" + name + ".xml");

                log.debug("Calling create() on context");
                context.create();
            } else if (AllowedCommands.DESTROY.getCommandName().equalsIgnoreCase(command)) {
                String suffix = parseSuffix(context.getEnvironment().getName());
                context.getEnvironment().setSuffix(suffix);
                log.debug("found suffix " + suffix);
                // write out instance failure regardless of success or failure
                outputXmlFile = inputXmlFile;
                log.debug("Calling destroy() on context");
                context.destroy();
            } else if (AllowedCommands.SUSPEND.getCommandName().equalsIgnoreCase(command)) {
                outputXmlFile = inputXmlFile;
                log.debug("Calling restore() on context");
                log.info("Attempting to suspend power on all instances/VMs in the environment.");
                context.restore();
                if (context instanceof ContextVmware) {
                    SuspendCommand newCommand = new SuspendCommand((ContextVmware) context);
                    newCommand.execute();
                } else if (context instanceof ContextAWS) {
                    com.urbancode.terraform.commands.aws.SuspendCommand newCommand = new com.urbancode.terraform.commands.aws.SuspendCommand(
                            (ContextAWS) context);
                    newCommand.execute();
                } else {
                    log.warn("Could not resolve context to call command \"" + command + "\"");
                }
            } else if (AllowedCommands.RESUME.getCommandName().equalsIgnoreCase(command)) {
                outputXmlFile = inputXmlFile;
                log.debug("Calling restore() on context");
                context.restore();
                log.info("Attempting to power on all instances/VMs in the environment.");
                if (context instanceof ContextVmware) {
                    ResumeCommand newCommand = new ResumeCommand((ContextVmware) context);
                    newCommand.execute();
                } else if (context instanceof ContextAWS) {
                    com.urbancode.terraform.commands.aws.ResumeCommand newCommand = new com.urbancode.terraform.commands.aws.ResumeCommand(
                            (ContextAWS) context);
                    newCommand.execute();
                }

                else {
                    log.warn("Could not resolve context to call command \"" + command + "\"");
                }
            } else if (AllowedCommands.TAKE_SNAPSHOT.getCommandName().equalsIgnoreCase(command)) {
                outputXmlFile = inputXmlFile;
                log.debug("Calling restore() on context");
                context.restore();
                log.info("Attempting to take snapshots of all instances/VMs in the environment.");
                if (context instanceof ContextVmware) {
                    TakeSnapshotCommand newCommand = new TakeSnapshotCommand((ContextVmware) context);
                    newCommand.execute();
                } else if (context instanceof ContextAWS) {
                    log.warn("Taking snapshots is not currently supported with Terraform and AWS.");
                }

                else {
                    log.warn("Could not resolve context to call command \"" + command + "\"");
                }
            }
        } catch (ParserConfigurationException e1) {
            throw new XmlParsingException("ParserConfigurationException: " + e1.getMessage(), e1);
        } catch (SAXException e2) {
            throw new XmlParsingException("SAXException: " + e2.getMessage(), e2);
        } finally {
            if (context != null && context.doWriteContext() && outputXmlFile != null) {
                log.debug("Writing context out to " + outputXmlFile);
                writeEnvToXml(outputXmlFile, context);
            }
        }
    }

    //----------------------------------------------------------------------------------------------
    private PropertyResolver createResolver() {
        return new PropertyResolver(props);
    }

    //----------------------------------------------------------------------------------------------
    private Credentials parseCredentials(File credsFile) {
        Credentials result = null;

        Properties credProps = loadPropertiesFromFile(credsFile);

        for (Property cmdlineProp : props) {
            if (credProps.get(cmdlineProp.getName()) == null) {
                credProps.put(cmdlineProp.getName(), cmdlineProp.getValue());
            }
        }

        result = parseCredsFromProps(credProps);

        if (result != null) {
            log.info("Restored Credentials: " + credsFile + " : " + result.getName());
        } else {
            log.info("Did not restore Credentials for " + credsFile);
        }

        return result;
    }

    //----------------------------------------------------------------------------------------------
    private Properties loadPropertiesFromFile(File propFile) {
        Properties result = new Properties();

        String path = propFile.getAbsolutePath();

        InputStream in = null;
        try {
            in = new FileInputStream(propFile);
            result.load(in);
        } catch (FileNotFoundException e) {
            log.error("Unable to load properties from " + path, e);
        } catch (IOException e) {
            log.error("IOException when loading properties from " + path, e);
            // swallow
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                // swallow
            }
        }

        return result;
    }

    //----------------------------------------------------------------------------------------------
    private Credentials parseCredsFromProps(Properties props) {
        Credentials result = null;

        String type = props.getProperty("type");

        if (type == null || "".equals(type)) {
            throw new NullPointerException("No credentials type specified in props: " + props);
        }

        CredentialsParser parser = CredentialsParserRegistry.getInstance().getParser(type);

        result = parser.parse(props);

        return result;
    }

    //----------------------------------------------------------------------------------------------
    private TerraformContext parseContext(File xmlFileToRead)
            throws ParserConfigurationException, XmlParsingException, SAXException, IOException {
        TerraformContext result = null;

        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        dbFactory.setNamespaceAware(true);
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(xmlFileToRead);
        Element rootElement = doc.getDocumentElement();
        rootElement.normalize();

        XmlModelParser parser = new XmlModelParser();
        parser.setPropertyResolver(createResolver());
        result = (TerraformContext) parser.parse(rootElement);

        return result;
    }

    //----------------------------------------------------------------------------------------------
    private Property parseProperty(String arg) throws IOException {
        String[] args;
        Property result = null;
        if (arg != null && arg.contains("=") && (args = arg.split("=")).length == 2) {
            result = new Property(args[0], args[1]);
        } else {
            log.error("bad property! Check your format. \nFound: " + arg);
            throw new IOException("bad parameter format");
        }
        return result;
    }

    //----------------------------------------------------------------------------------------------
    public void writeEnvToXml(File file, TerraformContext context) throws XmlParsingException {
        try {
            XmlWrite write = new XmlWrite();
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.newDocument();
            write.makeXml(context, doc, null);
            write.writeDocToFile(file, doc);
        } catch (Exception e) {
            throw new XmlParsingException("Exception while writing out XML: " + e.getMessage(), e);
        }

    }

    //----------------------------------------------------------------------------------------------
    private void startCredsParser() {
        CredentialsParserRegistry.getInstance().register(CredentialsAWS.class.getName(),
                CredentialsParserAWS.class);
        CredentialsParserRegistry.getInstance().register(CredentialsVmware.class.getName(),
                CredentialsParserVmware.class);
        CredentialsParserRegistry.getInstance().register(CredentialsMicrosoft.class.getName(),
                CredentialsParserMicrosoft.class);
        CredentialsParserRegistry.getInstance().register(CredentialsRackspace.class.getName(),
                CredentialsParserRackspace.class);
        CredentialsParserRegistry.getInstance().register(CredentialsVCloud.class.getName(),
                CredentialsParserVCloud.class);
    }

    //----------------------------------------------------------------------------------------------
    private String parseSuffix(String envName) {
        String result = null;
        try {
            String suffix = envName.substring(envName.length() - 4);
            if (matchesBase62(suffix)) {
                result = suffix;
            }
        } catch (IndexOutOfBoundsException e) {
            //swallow
        }

        return result;
    }

    //----------------------------------------------------------------------------------------------
    private boolean matchesBase62(String s) {
        return s.matches("\\A\\b[0-9a-zA-Z]+\\b\\Z");
    }
}