Package br.com.ingenieux.mojo.beanstalk.env

Source Code of br.com.ingenieux.mojo.beanstalk.env.ReplaceEnvironmentMojo

package br.com.ingenieux.mojo.beanstalk.env;

/*
* 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.
*/

import static org.apache.commons.lang.StringUtils.isBlank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.plugin.AbstractMojoExecutionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.jfrog.maven.annomojo.annotations.MojoGoal;
import org.jfrog.maven.annomojo.annotations.MojoParameter;
import org.jfrog.maven.annomojo.annotations.MojoSince;

import br.com.ingenieux.mojo.beanstalk.cmd.env.swap.SwapCNamesCommand;
import br.com.ingenieux.mojo.beanstalk.cmd.env.swap.SwapCNamesContext;
import br.com.ingenieux.mojo.beanstalk.cmd.env.swap.SwapCNamesContextBuilder;
import br.com.ingenieux.mojo.beanstalk.cmd.env.terminate.TerminateEnvironmentCommand;
import br.com.ingenieux.mojo.beanstalk.cmd.env.terminate.TerminateEnvironmentContext;
import br.com.ingenieux.mojo.beanstalk.cmd.env.terminate.TerminateEnvironmentContextBuilder;
import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentCommand;
import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentContext;
import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentContextBuilder;

import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionSetting;
import com.amazonaws.services.elasticbeanstalk.model.CreateEnvironmentResult;
import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsRequest;
import com.amazonaws.services.elasticbeanstalk.model.DescribeConfigurationSettingsResult;
import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription;

/**
* Launches a new environment and, when done, replace with the existing,
* terminating when needed. It combines both create-environment,
* wait-for-environment, swap-environment-cnames, and terminate-environment
*
*/
@MojoGoal("replace-environment")
@MojoSince("0.2.0")
// Best Guess Evar
public class ReplaceEnvironmentMojo extends CreateEnvironmentMojo {
  /**
   *
   */
  private static final Pattern PATTERN_NUMBERED = Pattern
      .compile("^(.*)-(\\d+)$");

  /**
   * Max Environment Name
   */
  private static final int MAX_ENVNAME_LEN = 23;

  /**
   * Minutes until timeout
   */
  @MojoParameter(expression = "${beanstalk.timeoutMins}", defaultValue = "20")
  Integer timeoutMins;

  @Override
  protected EnvironmentDescription handleResults(String kind,
      List<EnvironmentDescription> environments)
      throws MojoExecutionException {
    // Don't care - We're an exception to the rule, you know.

    return null;
  }

  @Override
  protected Object executeInternal() throws AbstractMojoExecutionException {
    /*
     * Is the desired cname not being used by other environments? If so,
     * just launch the environment
     */
    if (!hasEnvironmentFor(applicationName, cnamePrefix)) {
      if (getLog().isInfoEnabled())
        getLog().info("Just launching a new environment.");

      return super.executeInternal();
    }

    /*
     * Gets the current environment using this cname
     */
    EnvironmentDescription curEnv = getEnvironmentFor(applicationName,
        cnamePrefix);

    /*
     * Decides on a cnamePrefix, and launches a new environment
     */
    String cnamePrefixToCreate = getCNamePrefixToCreate();

    if (getLog().isInfoEnabled())
      getLog().info(
          "Creating a new environment on " + cnamePrefixToCreate
              + ".elasticbeanstalk.com");

    copyOptionSettings(curEnv);

    String newEnvironmentName = getNewEnvironmentName(this.environmentName);

    if (getLog().isInfoEnabled())
      getLog().info("And it'll be named " + newEnvironmentName);

    CreateEnvironmentResult createEnvResult = createEnvironment(
        cnamePrefixToCreate, newEnvironmentName);

    /*
     * Waits for completion
     */
    EnvironmentDescription newEnvDesc = null;

    try {
      newEnvDesc = waitForEnvironment(createEnvResult.getEnvironmentId());
    } catch (Exception exc) {
      /*
       * Terminates the failed launched environment
       */
      terminateAndWaitForEnvironment(createEnvResult.getEnvironmentId());

      handleException(exc);

      return null;
    }

    /*
     * Swaps
     */
    swapEnvironmentCNames(newEnvDesc.getEnvironmentId(),
        curEnv.getEnvironmentId(), cnamePrefix);

    /*
     * Terminates the previous environment - and waits for it
     */
    terminateAndWaitForEnvironment(curEnv.getEnvironmentId());

    return createEnvResult;
  }

  /**
   * Prior to Launching a New Environment, lets look and copy the most we can
   *
   * @param curEnv
   *            current environment
   */
  private void copyOptionSettings(EnvironmentDescription curEnv) {
    /**
     * Skip if we don't have anything
     */
    if (null != this.optionSettings && this.optionSettings.length > 0)
      return;

    DescribeConfigurationSettingsResult configSettings = getService()
        .describeConfigurationSettings(
            new DescribeConfigurationSettingsRequest()
                .withApplicationName(applicationName)
                .withEnvironmentName(
                    curEnv.getEnvironmentName()));

    List<ConfigurationOptionSetting> newOptionSettings = new ArrayList<ConfigurationOptionSetting>(
        configSettings.getConfigurationSettings().get(0)
            .getOptionSettings());

    ListIterator<ConfigurationOptionSetting> listIterator = newOptionSettings
        .listIterator();

    while (listIterator.hasNext()) {
      ConfigurationOptionSetting curOptionSetting = listIterator.next();

      /*
       * Filters out harmful options
       *
       * I really mean harmful - If you mention a terminated environment
       * settings, Elastic Beanstalk will accept, but this might lead to
       * inconsistent states, specially when creating / listing environments.
       *
       * Trust me on this one.
       */
      boolean bInvalid = isBlank(curOptionSetting.getValue());

      if (!bInvalid)
        bInvalid |= (curOptionSetting.getNamespace().equals(
            "aws:cloudformation:template:parameter") && curOptionSetting
            .getOptionName().equals("AppSource"));

      if (!bInvalid)
        bInvalid |= (curOptionSetting.getValue().contains(curEnv
            .getEnvironmentId()));

      if (bInvalid)
        listIterator.remove();
    }

    /*
     * Then copy it back
     */
    this.optionSettings = newOptionSettings
        .toArray(new ConfigurationOptionSetting[newOptionSettings
            .size()]);
  }

  /**
   * Swaps environment cnames
   *
   * @param newEnvironmentId
   *            environment id
   * @param curEnvironmentId
   *            environment id
   * @param cnamePrefix
   * @throws AbstractMojoExecutionException
   */
  protected void swapEnvironmentCNames(String newEnvironmentId,
      String curEnvironmentId, String cnamePrefix)
      throws AbstractMojoExecutionException {
    getLog().info(
        "Swapping environment cnames " + newEnvironmentId + " and "
            + curEnvironmentId);

    {
      SwapCNamesContext context = SwapCNamesContextBuilder
          .swapCNamesContext()//
          .withSourceEnvironmentId(newEnvironmentId)//
          .withDestinationEnvironmentId(curEnvironmentId)//
          .build();
      SwapCNamesCommand command = new SwapCNamesCommand(this);

      command.execute(context);
    }

    {
      WaitForEnvironmentContext context = new WaitForEnvironmentContextBuilder()
          .withApplicationName(applicationName)//
          .withStatusToWaitFor("Ready")//
          .withEnvironmentId(newEnvironmentId)//
          .withTimeoutMins(timeoutMins)//
          .withDomainToWaitFor(cnamePrefix).build();

      WaitForEnvironmentCommand command = new WaitForEnvironmentCommand(
          this);

      command.execute(context);
    }
  }

  /**
   * Terminates and waits for an environment
   *
   * @param environmentId
   *            environment id to terminate
   * @throws AbstractMojoExecutionException
   */
  protected void terminateAndWaitForEnvironment(String environmentId)
      throws AbstractMojoExecutionException {
    {
      getLog().info("Terminating environmentId=" + environmentId);

      TerminateEnvironmentContext terminatecontext = new TerminateEnvironmentContextBuilder()
          .withEnvironmentId(environmentId)
          .withTerminateResources(true).build();
      TerminateEnvironmentCommand command = new TerminateEnvironmentCommand(
          this);

      command.execute(terminatecontext);
    }

    {
      WaitForEnvironmentContext context = new WaitForEnvironmentContextBuilder()
          .withApplicationName(applicationName)
          .withEnvironmentId(environmentId)
          .withStatusToWaitFor("Terminated")
          .withTimeoutMins(timeoutMins).build();

      WaitForEnvironmentCommand command = new WaitForEnvironmentCommand(
          this);

      command.execute(context);
    }
  }

  /**
   * Waits for an environment to get ready. Throws an exception either if this
   * environment couldn't get into Ready state or there was a timeout
   *
   * @param environmentId
   *            environmentId to wait for
   * @return EnvironmentDescription in Ready state
   * @throws AbstractMojoExecutionException
   */
  protected EnvironmentDescription waitForEnvironment(String environmentId)
      throws AbstractMojoExecutionException {
    getLog().info(
        "Waiting for environmentId " + environmentId
            + " to get into Ready state");

    WaitForEnvironmentContext context = new WaitForEnvironmentContextBuilder()
        .withApplicationName(applicationName)
        .withStatusToWaitFor("Ready").withEnvironmentId(environmentId)
        .withTimeoutMins(timeoutMins).build();

    WaitForEnvironmentCommand command = new WaitForEnvironmentCommand(this);

    return command.execute(context);
  }

  /**
   * Creates a cname prefix if needed, or returns the desired one
   *
   * @return cname prefix to launch environment into
   */
  protected String getCNamePrefixToCreate() {
    String cnamePrefixToReturn = cnamePrefix;
    int i = 0;

    while (hasEnvironmentFor(applicationName, cnamePrefixToReturn))
      cnamePrefixToReturn = String.format("%s-%d", cnamePrefix, i++);

    return cnamePrefixToReturn;
  }

  /**
   * Boolean predicate for environment existence
   *
   * @param applicationName
   *            application name
   * @param cnamePrefix
   *            cname prefix
   * @return true if the application name has this cname prefix
   */
  protected boolean hasEnvironmentFor(String applicationName,
      String cnamePrefix) {
    return null != getEnvironmentFor(applicationName, cnamePrefix);
  }

  /**
   * Returns the environment description matching applicationName and
   * cnamePrefix
   *
   * @param applicationName
   *            application name
   * @param cnamePrefix
   *            cname prefix
   * @return environment description
   */
  protected EnvironmentDescription getEnvironmentFor(String applicationName,
      String cnamePrefix) {
    Collection<EnvironmentDescription> environments = getEnvironmentsFor(applicationName);
    String cnameToMatch = String.format("%s.elasticbeanstalk.com",
        cnamePrefix);

    /*
     * Finds a matching environment
     */
    for (EnvironmentDescription envDesc : environments)
      if (envDesc.getCNAME().equals(cnameToMatch))
        return envDesc;

    return null;
  }

  private String getNewEnvironmentName(String newEnvironmentName) {
    String result = newEnvironmentName;
    String environmentRadical = result;

    int i = 0;

    {
      Matcher matcher = PATTERN_NUMBERED.matcher(newEnvironmentName);

      if (matcher.matches()) {
        environmentRadical = matcher.group(1);

        i = 1 + Integer.valueOf(matcher.group(2));
      }
    }

    while (containsNamedEnvironment(result))
      result = formatAndTruncate("%s-%d", MAX_ENVNAME_LEN,
          environmentRadical, i++);

    return result;
  }

  /**
   * Elastic Beanstalk Contains a Max EnvironmentName Limit. Lets truncate it,
   * shall we?
   *
   * @param mask
   *            String.format Mask
   * @param maxLen
   *            Maximum Length
   * @param args
   *            String.format args
   * @return formatted String, or maxLen rightmost characters
   */
  protected String formatAndTruncate(String mask, int maxLen, Object... args) {
    String result = String.format(mask, args);

    if (result.length() > maxLen)
      result = result
          .substring(result.length() - maxLen, result.length());

    return result;
  }

  /**
   * Boolean predicate for named environment
   *
   * @param environmentName
   *            environment name
   * @return true if environment name exists
   */
  protected boolean containsNamedEnvironment(String environmentName) {
    for (EnvironmentDescription envDesc : getEnvironmentsFor(applicationName))
      if (envDesc.getEnvironmentName().equals(environmentName))
        return true;

    return false;
  }
}
TOP

Related Classes of br.com.ingenieux.mojo.beanstalk.env.ReplaceEnvironmentMojo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.