/* * Copyright (c) 2016 ingenieux Labs * * 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 br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; import com.amazonaws.regions.Region; import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest; import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsRequest; import com.amazonaws.services.elasticbeanstalk.model.DescribeEventsResult; import com.amazonaws.services.elasticbeanstalk.model.EnvironmentDescription; import com.amazonaws.services.elasticbeanstalk.model.EventDescription; import org.apache.commons.lang.Validate; import org.apache.maven.plugin.MojoExecutionException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import br.com.ingenieux.mojo.beanstalk.AbstractBeanstalkMojo; import br.com.ingenieux.mojo.beanstalk.cmd.BaseCommand; import br.com.ingenieux.mojo.beanstalk.util.EnvironmentHostnameUtil; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.defaultString; import static org.apache.commons.lang.StringUtils.isNotBlank; public class WaitForEnvironmentCommand extends BaseCommand<WaitForEnvironmentContext, EnvironmentDescription> { /** * Poll Interval */ public static final long POLL_INTERVAL = 30 * 1000; /** * Magic Constant for Mins to MSEC */ private static final long MINS_TO_MSEC = 60 * 1000; /** * Constructor * * @param parentMojo parent mojo */ public WaitForEnvironmentCommand(AbstractBeanstalkMojo parentMojo) throws MojoExecutionException { super(parentMojo); } public Collection<EnvironmentDescription> lookupInternal(WaitForEnvironmentContext context) { List<Predicate<EnvironmentDescription>> envPredicates = getEnvironmentDescriptionPredicate(context); DescribeEnvironmentsRequest req = new DescribeEnvironmentsRequest().withApplicationName(context.getApplicationName()).withIncludeDeleted(true); final List<EnvironmentDescription> envs = parentMojo.getService().describeEnvironments(req).getEnvironments(); return Collections2.filter(envs, Predicates.and(envPredicates)); } protected List<Predicate<EnvironmentDescription>> getEnvironmentDescriptionPredicate(WaitForEnvironmentContext context) { // as well as those (which are used as predicate variables, thus being // final) final String environmentRef = context.getEnvironmentRef(); final String statusToWaitFor = defaultString(context.getStatusToWaitFor(), "!Terminated"); final String healthToWaitFor = context.getHealth(); // Sanity Check Validate.isTrue(isNotBlank(environmentRef), "EnvironmentRef is blank or null", environmentRef); // some argument juggling final boolean negated = statusToWaitFor.startsWith("!"); // argument juggling List<Predicate<EnvironmentDescription>> result = new ArrayList<Predicate<EnvironmentDescription>>(); Matcher hostMatcher = EnvironmentHostnameUtil.PATTERN_HOSTNAME.matcher(environmentRef); if (environmentRef.matches("e-\\p{Alnum}{10}")) { result.add( new Predicate<EnvironmentDescription>() { @Override public boolean apply(EnvironmentDescription t) { return t.getEnvironmentId().equals(environmentRef); } }); info("... with environmentId equal to '%s'", environmentRef); } else if (hostMatcher.matches()) { final Region region = parentMojo.getRegion(); final String cnamePrefix = hostMatcher.group("cnamePrefix"); final Predicate<EnvironmentDescription> predicate = EnvironmentHostnameUtil.getHostnamePredicate(region, cnamePrefix); result.add(predicate); info(predicate.toString()); } else { String tmpRE = Pattern.quote(environmentRef); if (environmentRef.endsWith("*")) { tmpRE = format("^\\Q%s\\E.*", environmentRef.substring(0, -1 + environmentRef.length())); } final String environmentRefNameRE = tmpRE; result.add( new Predicate<EnvironmentDescription>() { @Override public boolean apply(EnvironmentDescription t) { return t.getEnvironmentName().matches(environmentRefNameRE); } }); info("... with environmentName matching re '%s'", environmentRefNameRE); } { // start building predicates with the status one - "![status]" must // be equal to status or not status final int offset = negated ? 1 : 0; final String vStatusToWaitFor = statusToWaitFor.substring(offset); result.add( new Predicate<EnvironmentDescription>() { public boolean apply(EnvironmentDescription t) { boolean result = vStatusToWaitFor.equals(t.getStatus()); if (negated) { result = !result; } debug("testing status '%s' as equal as '%s' (negated? %s, offset: %d): %s", vStatusToWaitFor, t.getStatus(), negated, offset, result); return result; } }); info("... with status %s set to '%s'", (negated ? "*NOT*" : " "), vStatusToWaitFor); } { if (isNotBlank(healthToWaitFor)) { result.add( new Predicate<EnvironmentDescription>() { @Override public boolean apply(EnvironmentDescription t) { return t.getHealth().equals(healthToWaitFor); } }); info("... with health equal to '%s'", healthToWaitFor); } } return result; } public EnvironmentDescription executeInternal(WaitForEnvironmentContext context) throws Exception { // Those are invariants long timeoutMins = context.getTimeoutMins(); Date expiresAt = new Date(System.currentTimeMillis() + MINS_TO_MSEC * timeoutMins); Date lastMessageRecord = new Date(); info("Environment Lookup"); List<Predicate<EnvironmentDescription>> envPredicates = getEnvironmentDescriptionPredicate(context); Predicate<EnvironmentDescription> corePredicate = envPredicates.get(0); Predicate<EnvironmentDescription> fullPredicate = Predicates.and(envPredicates); do { DescribeEnvironmentsRequest req = new DescribeEnvironmentsRequest().withApplicationName(context.getApplicationName()).withIncludeDeleted(true); final List<EnvironmentDescription> envs = parentMojo.getService().describeEnvironments(req).getEnvironments(); Collection<EnvironmentDescription> validEnvironments = Collections2.filter(envs, fullPredicate); debug("There are %d environments", validEnvironments.size()); if (1 == validEnvironments.size()) { EnvironmentDescription foundEnvironment = validEnvironments.iterator().next(); debug("Found environment %s", foundEnvironment); return foundEnvironment; } else { debug("Found %d environments. No good. Ignoring.", validEnvironments.size()); for (EnvironmentDescription d : validEnvironments) { debug(" ... %s", d); } // ... but have we've got any closer match? If so, dump recent events Collection<EnvironmentDescription> foundEnvironments = Collections2.filter(envs, corePredicate); if (1 == foundEnvironments.size()) { EnvironmentDescription foundEnvironment = foundEnvironments.iterator().next(); DescribeEventsResult events = service.describeEvents( new DescribeEventsRequest() .withApplicationName(foundEnvironment.getApplicationName()) .withStartTime(new Date(1000 + lastMessageRecord.getTime())) .withEnvironmentId(foundEnvironment.getEnvironmentId()) .withSeverity("TRACE")); Set<EventDescription> eventList = new TreeSet<EventDescription>(new EventDescriptionComparator()); eventList.addAll(events.getEvents()); for (EventDescription d : eventList) { info("%s %s %s", d.getSeverity(), d.getEventDate(), d.getMessage()); if (d.getSeverity().equals(("ERROR"))) { throw new MojoExecutionException("Something went wrong in while waiting for the environment setup to complete : " + d.getMessage()); } lastMessageRecord = d.getEventDate(); } } } sleepInterval(POLL_INTERVAL); } while (!timedOutP(expiresAt)); throw new MojoExecutionException("Timed out"); } boolean timedOutP(Date expiresAt) throws MojoExecutionException { return expiresAt.before(new Date(System.currentTimeMillis())); } public void sleepInterval(long pollInterval) { debug("Sleeping for %d seconds", pollInterval / 1000); try { Thread.sleep(pollInterval); } catch (InterruptedException e) { } } static class EventDescriptionComparator implements Comparator<EventDescription> { @Override public int compare(EventDescription o1, EventDescription o2) { return o1.getEventDate().compareTo(o2.getEventDate()); } } }