View Javadoc
1   /**
2    * Copyright (c) 2012-2014, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.beanstalk.maven.plugin;
31  
32  import com.amazonaws.auth.AWSCredentials;
33  import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk;
34  import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalkClient;
35  import com.amazonaws.services.s3.AmazonS3Client;
36  import com.jcabi.aspects.Tv;
37  import com.jcabi.log.Logger;
38  import java.io.File;
39  import java.io.IOException;
40  import java.util.concurrent.TimeUnit;
41  import java.util.zip.ZipFile;
42  import org.apache.maven.plugin.AbstractMojo;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.settings.Settings;
45  import org.jfrog.maven.annomojo.annotations.MojoParameter;
46  import org.slf4j.impl.StaticLoggerBinder;
47  
48  /**
49   * Abstract MOJO for this plugin.
50   *
51   * @author Yegor Bugayenko (yegor@tpc2.com)
52   * @version $Id$
53   * @since 0.7.1
54   * @checkstyle ClassDataAbstractionCoupling (500 lines)
55   */
56  abstract class AbstractBeanstalkMojo extends AbstractMojo {
57      /**
58       * Setting.xml.
59       */
60      @MojoParameter(
61          expression = "${settings}",
62          required = true,
63          readonly = true,
64          description = "Maven settings.xml reference"
65      )
66      private transient Settings settings;
67  
68      /**
69       * Shall we skip execution?
70       */
71      @MojoParameter(
72          defaultValue = "false",
73          required = false,
74          description = "Skips execution"
75      )
76      private transient boolean skip;
77  
78      /**
79       * Server ID to deploy to.
80       */
81      @MojoParameter(
82          defaultValue = "aws.amazon.com",
83          required = false,
84          description = "ID of the server to deploy to, from settings.xml"
85      )
86      private transient String server;
87  
88      /**
89       * Application name (also the name of environment and CNAME).
90       */
91      @MojoParameter(
92          required = true,
93          description = "EBT application name, environment name, and CNAME"
94      )
95      private transient String name;
96  
97      /**
98       * S3 bucket.
99       */
100     @MojoParameter(
101         required = true,
102         description = "Amazon S3 bucket name where to upload WAR file"
103     )
104     private transient String bucket;
105 
106     /**
107      * S3 key name.
108      */
109     @MojoParameter(
110         required = true,
111         description = "Amazon S3 bucket key where to upload WAR file"
112     )
113     private transient String key;
114 
115     /**
116      * Template name.
117      */
118     @MojoParameter(
119         required = true,
120         description = "Amazon Elastic Beanstalk configuration template name"
121     )
122     private transient String template;
123 
124     /**
125      * WAR file to deploy.
126      * @checkstyle LineLength (3 lines)
127      */
128     @MojoParameter(
129         defaultValue = "${project.build.directory}/${project.build.finalName}.war",
130         required = false,
131         description = "Location of .WAR file to deploy"
132     )
133     private transient File war;
134 
135     /**
136      * Set skip option.
137      * @param skp Shall we skip execution?
138      */
139     public void setSkip(final boolean skp) {
140         this.skip = skp;
141     }
142 
143     /**
144      * {@inheritDoc}
145      */
146     @Override
147     public void execute() throws MojoFailureException {
148         StaticLoggerBinder.getSingleton().setMavenLog(this.getLog());
149         if (this.skip) {
150             Logger.info(this, "execution skipped because of 'skip' option");
151             return;
152         }
153         if (!this.war.exists()) {
154             throw new MojoFailureException(
155                 String.format("WAR file '%s' doesn't exist", this.war)
156             );
157         }
158         try {
159             new WarFile(new ZipFile(this.war)).checkEbextensionsValidity();
160         } catch (final IOException ex) {
161             throw new MojoFailureException(
162                 ".ebextensions validity check failed",
163                 ex
164             );
165         }
166         final AWSCredentials creds = this.createServerCredentials();
167         final AWSElasticBeanstalk ebt = new AWSElasticBeanstalkClient(creds);
168         try {
169             this.exec(
170                 new Application(ebt, this.name),
171                 new OverridingVersion(
172                     ebt,
173                     this.name,
174                     new Bundle.Safe(
175                         new OverridingBundle(
176                             new AmazonS3Client(creds),
177                             this.bucket,
178                             this.key,
179                             this.war
180                         )
181                     )
182                 ),
183                 this.template
184             );
185         } catch (final DeploymentException ex) {
186             throw new MojoFailureException("failed to deploy", ex);
187         } finally {
188             ebt.shutdown();
189         }
190     }
191 
192     /**
193      * Creates server crecentials.
194      * @return Server credentials based on settings and server attributes.
195      * @throws MojoFailureException Thrown in case of error.
196      */
197     protected ServerCredentials createServerCredentials()
198         throws MojoFailureException {
199         return new ServerCredentials(
200             this.settings,
201             this.server
202         );
203     }
204 
205     /**
206      * Deploy using this EBT client.
207      * @param app Application to deploy to
208      * @param version Version to deploy
209      * @param tmpl Template to use
210      */
211     protected abstract void exec(final Application app,
212         final Version version, final String tmpl);
213 
214     /**
215      * Report when environment is failed.
216      * @param env The environment
217      */
218     protected void postMortem(final Environment env) {
219         Logger.error(this, "Failed to deploy to '%s'", env);
220         if (!env.terminated()) {
221             Logger.error(
222                 this,
223                 "TAIL report should explain the cause of failure:"
224             );
225             this.log(env.tail().split("\n"));
226         }
227         Logger.error(this, "Latest EBT events (in reverse order):");
228         this.log(env.events());
229         env.terminate();
230     }
231 
232     /**
233      * Wait for green status.
234      * @param env The environment
235      * @return TRUE if green
236      */
237     protected boolean isGreen(final Environment env) {
238         boolean green = env.green();
239         final long start = System.currentTimeMillis();
240         while (!green) {
241             final long age = System.currentTimeMillis() - start;
242             if (age > TimeUnit.MINUTES.toMillis(Tv.FIFTEEN)) {
243                 Logger.warn(this, "Waiting for %[ms]s, time to give up", age);
244                 break;
245             }
246             Logger.warn(
247                 this,
248                 "%s is not GREEN yet, let's wait another 15 second...", env
249             );
250             try {
251                 TimeUnit.SECONDS.sleep(Tv.FIFTEEN);
252             } catch (final InterruptedException ex) {
253                 Thread.currentThread().interrupt();
254                 throw new DeploymentException(ex);
255             }
256             green = env.green();
257         }
258         return green;
259     }
260 
261     /**
262      * Log all lines from the collection.
263      * @param lines All lines to log
264      */
265     private void log(final String[] lines) {
266         for (final String line : lines) {
267             Logger.info(this, ">> %s", line);
268         }
269     }
270 }