View Javadoc

1   package org.kuali.maven.ec2;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.util.Collections;
6   import java.util.List;
7   import java.util.Properties;
8   
9   import org.apache.commons.codec.binary.Base64;
10  import org.apache.commons.io.IOUtils;
11  import org.apache.maven.plugin.MojoExecutionException;
12  import org.apache.maven.project.MavenProject;
13  import org.codehaus.plexus.util.StringUtils;
14  import org.kuali.maven.common.PropertiesUtils;
15  import org.kuali.maven.common.ResourceUtils;
16  
17  import com.amazonaws.services.ec2.AmazonEC2;
18  import com.amazonaws.services.ec2.model.CreateTagsRequest;
19  import com.amazonaws.services.ec2.model.Instance;
20  import com.amazonaws.services.ec2.model.InstanceType;
21  import com.amazonaws.services.ec2.model.Reservation;
22  import com.amazonaws.services.ec2.model.RunInstancesRequest;
23  import com.amazonaws.services.ec2.model.RunInstancesResult;
24  import com.amazonaws.services.ec2.model.Tag;
25  
26  /**
27   * Connect to EC2 and launch a a single instance configured according to user preferences. By default, the plugin waits
28   * until the instance reaches the state of "running" before allowing the build to continue. Once an EC2 instance is
29   * "running" Amazon has assigned it a public dns name. The publi dns name, the instance id, and the value of the tag
30   * "Name" (if that tag is supplied) are stored as the project properties <code>ec2.instance.dns</code>,
31   * <code>ec2.instance.id</code>, <code>ec2.instance.name</code>, respectively.
32   *
33   * If <code>wait</code> is false, the <code>ec2.instance.dns</code> property will not be set since the instance will not
34   * have a public dns name by the time the plugin execution completes.
35   *
36   * @goal launch
37   */
38  public class LaunchMojo extends AbstractEC2Mojo {
39  
40      /**
41       * The Maven project object
42       *
43       * @parameter expression="${project}"
44       * @readonly
45       */
46      private MavenProject project;
47  
48      /**
49       * The AMI to launch
50       *
51       * @parameter expression="${ec2.ami}"
52       * @required
53       */
54      private String ami;
55  
56      /**
57       * The name of the key to use
58       *
59       * @parameter expression="${ec2.key}"
60       * @required
61       */
62      private String key;
63  
64      /**
65       * The type of instance to launch
66       *
67       * @parameter expression="${ec2.type}" default-value="m1.medium";
68       * @required
69       */
70      private String type;
71  
72      /**
73       * The security groups into which the instance will be launched
74       *
75       * @parameter
76       */
77      private List<String> securityGroups;
78  
79      /**
80       * Optional user data for the instance
81       *
82       * @parameter expression="${ec2.userData}"
83       */
84      private String userData;
85  
86      /**
87       * If supplied, the contents of the file are supplied to the instance as userData. This can be a file on the file
88       * system, or any url Spring resource loading can understand eg "<code>classpath:user-data.txt</code>"
89       *
90       * @parameter expression="${ec2.userDataFile}"
91       */
92      private String userDataFile;
93  
94      /**
95       * If true, userData is filtered with current project, environment, and system properties before being supplied to
96       * the instance.
97       *
98       * @parameter expression="${ec2.filterUserData}
99       */
100     private boolean filterUserData;
101 
102     /**
103      * The encoding of the userDataFile
104      *
105      * @parameter expression="${ec2.encoding}" default-value="${project.build.sourceEncoding}"
106      */
107     private String encoding;
108 
109     /**
110      * List of tags to associate with the instance. Tags are key value pairs and can be supplied in the plugin
111      * configuration like this:<br>
112      *
113      * <pre>
114      *   &lt;tags&gt;
115      *     &lt;tag&gt;
116      *       &lt;key&gt;Name&lt;/key&gt;
117      *       &lt;value&gt;production&lt;/value&gt;
118      *     &lt;/tag&gt;
119      *     &lt;tag&gt;
120      *       &lt;key&gt;Category&lt;/key&gt;
121      *       &lt;value&gt;networking&lt;/value&gt;
122      *     &lt;/tag&gt;
123      *   &lt;/tags&gt;
124      * </pre>
125      *
126      * @parameter
127      */
128     private List<Tag> tags;
129 
130     /**
131      * If true, the build will wait until EC2 reports that the instance has reached the state of "running"
132      *
133      * @parameter expression="${ec2.wait}" default-value="true"
134      */
135     private boolean wait;
136 
137     /**
138      * The number of seconds to wait for the instance to start before timing out and failing the build
139      *
140      * @parameter expression="${ec2.waitTimeout}" default-value="300"
141      */
142     private int waitTimeout;
143 
144     /**
145      * The state the instance needs to be in before the plugin considers it to be started.
146      *
147      * @parameter expression="${ec2.state}" default-value="running"
148      */
149     private String state;
150 
151     @Override
152     public void execute() throws MojoExecutionException {
153         AmazonEC2 client = getEC2Client();
154         RunInstancesRequest request = getRequest();
155         Instance i = getInstance(client, request);
156         handleTags(client, i, tags);
157         wait(client, i);
158         Instance running = getInstance(client, i.getInstanceId());
159         Properties props = project.getProperties();
160         props.setProperty("ec2.instance.id", running.getInstanceId());
161         props.setProperty("ec2.instance.name", getTagValue(running, "Name"));
162     }
163 
164     protected RunInstancesRequest getRequest() throws MojoExecutionException {
165         RunInstancesRequest request = new RunInstancesRequest();
166         request.setMaxCount(1);
167         request.setMinCount(1);
168         request.setImageId(ami);
169         request.setKeyName(key);
170         request.setInstanceType(InstanceType.fromValue(type));
171         request.setSecurityGroups(securityGroups);
172         String data = getUserData(userData, userDataFile, encoding);
173         request.setUserData(data);
174         return request;
175     }
176 
177     protected Instance getInstance(AmazonEC2 client, RunInstancesRequest request) {
178         RunInstancesResult result = client.runInstances(request);
179         Reservation r = result.getReservation();
180         List<Instance> instances = r.getInstances();
181         return instances.get(0);
182     }
183 
184     protected void wait(AmazonEC2 client, Instance i) throws MojoExecutionException {
185         if (wait) {
186             getLog().info("Waiting up to " + waitTimeout + " seconds for " + i.getInstanceId() + " to start");
187             waitForState(client, i.getInstanceId(), state, waitTimeout);
188             Instance running = getInstance(client, i.getInstanceId());
189             String id = i.getInstanceId();
190             String dns = running.getPublicDnsName();
191             getLog().info("EC2 Instance: " + getTagValue(running, "Name") + " (" + id + ") " + dns);
192             Properties props = project.getProperties();
193             props.setProperty("ec2.instance.dns", running.getPublicDnsName());
194         } else {
195             getLog().info("Launched " + i.getInstanceId());
196         }
197     }
198 
199     protected void handleTags(AmazonEC2 client, Instance instance, List<Tag> tags) {
200         if (isEmpty(tags)) {
201             return;
202         }
203         CreateTagsRequest request = new CreateTagsRequest();
204         request.setResources(Collections.singletonList(instance.getInstanceId()));
205         request.setTags(tags);
206         client.createTags(request);
207     }
208 
209     protected String getUserData(String data, String location, String encoding) throws MojoExecutionException {
210         String s = data;
211         if (!StringUtils.isBlank(location)) {
212             try {
213                 s = getString(location, encoding);
214             } catch (IOException e) {
215                 throw new MojoExecutionException("Error reading from " + location, e);
216             }
217         }
218         if (StringUtils.isBlank(s)) {
219             return null;
220         }
221         if (filterUserData) {
222             PropertiesUtils pu = new PropertiesUtils();
223             Properties properties = pu.getMavenProperties(project);
224             s = pu.getResolvedValue(s, properties);
225         }
226         getLog().debug("filteredUserData=" + s);
227         byte[] bytes = Base64.encodeBase64(s.getBytes());
228         String base64 = new String(bytes);
229         getLog().debug("base64Encoded=" + base64);
230         return base64;
231     }
232 
233     protected String getString(String location, String encoding) throws IOException {
234         InputStream in = null;
235         try {
236             ResourceUtils ru = new ResourceUtils();
237             in = ru.getInputStream(location);
238             return IOUtils.toString(in, encoding);
239         } finally {
240             IOUtils.closeQuietly(in);
241         }
242     }
243 
244     public String getAmi() {
245         return ami;
246     }
247 
248     public void setAmi(String ami) {
249         this.ami = ami;
250     }
251 
252     public String getKey() {
253         return key;
254     }
255 
256     public void setKey(String key) {
257         this.key = key;
258     }
259 
260     public String getType() {
261         return type;
262     }
263 
264     public void setType(String type) {
265         this.type = type;
266     }
267 
268     public List<String> getSecurityGroups() {
269         return securityGroups;
270     }
271 
272     public void setSecurityGroups(List<String> securityGroups) {
273         this.securityGroups = securityGroups;
274     }
275 
276     public String getUserData() {
277         return userData;
278     }
279 
280     public void setUserData(String userData) {
281         this.userData = userData;
282     }
283 
284     public String getEncoding() {
285         return encoding;
286     }
287 
288     public void setEncoding(String encoding) {
289         this.encoding = encoding;
290     }
291 
292     public boolean isFilterUserData() {
293         return filterUserData;
294     }
295 
296     public void setFilterUserData(boolean filterUserData) {
297         this.filterUserData = filterUserData;
298     }
299 
300     public MavenProject getProject() {
301         return project;
302     }
303 
304     public List<Tag> getTags() {
305         return tags;
306     }
307 
308     public void setTags(List<Tag> tags) {
309         this.tags = tags;
310     }
311 
312     public boolean isWait() {
313         return wait;
314     }
315 
316     public void setWait(boolean wait) {
317         this.wait = wait;
318     }
319 
320     public int getWaitTimeout() {
321         return waitTimeout;
322     }
323 
324     public void setWaitTimeout(int waitTimeout) {
325         this.waitTimeout = waitTimeout;
326     }
327 
328     public String getState() {
329         return state;
330     }
331 
332     public void setState(String state) {
333         this.state = state;
334     }
335 
336     public String getUserDataFile() {
337         return userDataFile;
338     }
339 
340     public void setUserDataFile(String userDataFile) {
341         this.userDataFile = userDataFile;
342     }
343 }