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.heroku.maven.plugin;
31  
32  import com.jcabi.log.Logger;
33  import com.jcabi.velocity.VelocityPage;
34  import java.io.File;
35  import java.util.ArrayList;
36  import java.util.List;
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.artifact.DefaultArtifact;
39  import org.apache.maven.plugin.AbstractMojo;
40  import org.apache.maven.plugin.MojoFailureException;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.settings.Server;
43  import org.apache.maven.settings.Settings;
44  import org.jfrog.maven.annomojo.annotations.MojoGoal;
45  import org.jfrog.maven.annomojo.annotations.MojoParameter;
46  import org.jfrog.maven.annomojo.annotations.MojoPhase;
47  import org.slf4j.impl.StaticLoggerBinder;
48  
49  /**
50   * Deploys JAR/WAR artifact to Heroku.
51   *
52   * @author Yegor Bugayenko (yegor@tpc2.com)
53   * @version $Id$
54   * @since 0.4
55   */
56  @MojoGoal("deploy")
57  @MojoPhase("deploy")
58  public final class DeployMojo extends AbstractMojo {
59  
60      /**
61       * Maven project.
62       */
63      @MojoParameter(
64          expression = "${project}",
65          required = true,
66          readonly = true,
67          description = "Maven project"
68      )
69      private transient MavenProject project;
70  
71      /**
72       * Setting.xml.
73       */
74      @MojoParameter(
75          expression = "${settings}",
76          required = true,
77          readonly = true,
78          description = "Maven settings.xml reference"
79      )
80      private transient Settings settings;
81  
82      /**
83       * Shall we skip execution?
84       */
85      @MojoParameter(
86          defaultValue = "false",
87          required = false,
88          description = "Skips execution"
89      )
90      private transient boolean skip;
91  
92      /**
93       * Server ID from settings.xml.
94       */
95      @MojoParameter(
96          defaultValue = "heroku.com",
97          required = false,
98          description = "Server ID from settings.xml"
99      )
100     private transient String server;
101 
102     /**
103      * Application name.
104      */
105     @MojoParameter(
106         required = true,
107         description = "Heroku application name"
108     )
109     private transient String name;
110 
111     /**
112      * Content of {@code Procfile}.
113      */
114     @MojoParameter(
115         required = true,
116         description = "Content of Procfile"
117     )
118     private transient String procfile;
119 
120     /**
121      * List of artifacts to download.
122      */
123     @MojoParameter(
124         required = true,
125         description = "Artifacts to download during deployment"
126     )
127     private transient String[] artifacts;
128 
129     /**
130      * Set skip option.
131      * @param skp Shall we skip execution?
132      */
133     public void setSkip(final boolean skp) {
134         this.skip = skp;
135     }
136 
137     @Override
138     @SuppressWarnings("PMD.PrematureDeclaration")
139     public void execute() throws MojoFailureException {
140         StaticLoggerBinder.getSingleton().setMavenLog(this.getLog());
141         if (this.skip) {
142             Logger.info(this, "execution skipped because of 'skip' option");
143             return;
144         }
145         final long start = System.currentTimeMillis();
146         final Heroku heroku = new Heroku(this.git(), this.name);
147         final Repo repo = heroku.clone(
148             new File(new File(this.project.getBuild().getDirectory()), "heroku")
149         );
150         try {
151             repo.add(
152                 "settings.xml",
153                 new VelocityPage(
154                     "com/jcabi/heroku/maven/plugin/settings.xml.vm"
155                 ).set("settings", this.settings).toString()
156             );
157             repo.add(
158                 "pom.xml",
159                 new VelocityPage(
160                     "com/jcabi/heroku/maven/plugin/pom.xml.vm"
161                 ).set("project", this.project)
162                     .set("deps", this.deps())
163                     .set("timestamp", System.currentTimeMillis())
164                     .toString()
165             );
166             repo.add("Procfile", this.procfile.trim());
167         } catch (final java.io.IOException ex) {
168             throw new MojoFailureException("failed to save files", ex);
169         }
170         repo.commit();
171         Logger.info(this, "Done in %[ms]s", System.currentTimeMillis() - start);
172     }
173 
174     /**
175      * Get git engine.
176      * @return The engine
177      * @throws MojoFailureException If somethings goes wrong
178      */
179     private Git git() throws MojoFailureException {
180         final Server srv = this.settings.getServer(this.server);
181         if (srv == null) {
182             throw new MojoFailureException(
183                 String.format(
184                     "Server '%s' not found in settings.xml",
185                     this.server
186                 )
187             );
188         }
189         final String location = srv.getPrivateKey();
190         if (location == null || location.isEmpty()) {
191             throw new MojoFailureException(
192                 String.format(
193                     "privateKey is not defined for '%s' server in settings.xml",
194                     srv.getId()
195                 )
196             );
197         }
198         final File file = new File(location);
199         if (!file.exists()) {
200             throw new MojoFailureException(
201                 String.format("SSH key file '%s' doesn't exist", file)
202             );
203         }
204         try {
205             return new Git(
206                 file,
207                 new File(this.project.getBuild().getDirectory())
208             );
209         } catch (final java.io.IOException ex) {
210             throw new MojoFailureException("failed to initialize git", ex);
211         }
212     }
213 
214     /**
215      * Create a collection of artifacts.
216      *
217      * <p>Coordinates should be formatted as
218      * {@code groupId:artifactId:packaging:classifier:version}.
219      *
220      * @return List of them
221      * @throws MojoFailureException If somethings goes wrong
222      * @see <a href="http://maven.apache.org/pom.html#Maven_Coordinates">Maven coordinates</a>
223      */
224     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
225     private List<Artifact> deps() throws MojoFailureException {
226         if (this.artifacts.length == 0) {
227             throw new MojoFailureException(
228                 "At least one artifact should be configured"
229             );
230         }
231         final List<Artifact> deps = new ArrayList<Artifact>(
232             this.artifacts.length
233         );
234         for (final String coordinates : this.artifacts) {
235             final String[] parts = coordinates.split(":");
236             // @checkstyle MagicNumber (1 line)
237             if (parts.length != 5) {
238                 throw new MojoFailureException(
239                     String.format(
240                         "Maven artifact coordinates '%s' is not absolute",
241                         coordinates
242                     )
243                 );
244             }
245             deps.add(
246                 // @checkstyle MagicNumber (10 lines)
247                 new DefaultArtifact(
248                     parts[0],
249                     parts[1],
250                     parts[4],
251                     "runtime",
252                     parts[2],
253                     parts[3],
254                     null
255                 )
256             );
257         }
258         return deps;
259     }
260 
261 }