/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.amazontools;
import com.amazonaws.services.ec2.model.AvailabilityZone;
import com.amazonaws.services.ec2.model.CreateImageRequest;
import com.amazonaws.services.ec2.model.CreateImageResult;
import com.amazonaws.services.ec2.model.DeregisterImageRequest;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult;
import com.amazonaws.services.ec2.model.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeImagesResult;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Image;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceState;
import com.amazonaws.services.ec2.model.InstanceStateChange;
import com.amazonaws.services.ec2.model.Placement;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest;
import com.amazonaws.services.ec2.model.TerminateInstancesResult;
import com.amazonaws.util.json.JSONObject;
import jargs.gnu.CmdLineParser;
import java.io.File;
import java.net.ConnectException;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile;
import net.schmizz.sshj.xfer.FileSystemFile;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* @author Ivica Cardic
*/
public class AMIBuilder extends BaseAMITool {
public static void main(String[] args) throws Exception {
CmdLineParser cmdLineParser = new CmdLineParser();
CmdLineParser.Option baseDirOption = cmdLineParser.addStringOption(
"base.dir");
CmdLineParser.Option propertiesFileNameOption =
cmdLineParser.addStringOption("properties.file.name");
CmdLineParser.Option imageNameOption = cmdLineParser.addStringOption(
"image.name");
CmdLineParser.Option outputOption = cmdLineParser.addStringOption(
"output");
cmdLineParser.parse(args);
final AMIBuilder amiBuilder = new AMIBuilder(
(String)cmdLineParser.getOptionValue(baseDirOption),
(String)cmdLineParser.getOptionValue(imageNameOption),
Boolean.parseBoolean(
(String)cmdLineParser.getOptionValue(outputOption)),
(String)cmdLineParser.getOptionValue(propertiesFileNameOption));
Runtime runtime = Runtime.getRuntime();
runtime.addShutdownHook(
new Thread() {
@Override
public void run() {
amiBuilder.destroy();
}
});
try {
amiBuilder.start();
amiBuilder.provision();
amiBuilder.createImage();
amiBuilder.destroy();
}
catch (Exception e) {
e.printStackTrace();
amiBuilder.destroy();
System.exit(-1);
return;
}
System.exit(0);
}
public AMIBuilder(
String baseDirName, String imageName, boolean output,
String propertiesFileName)
throws Exception {
super(propertiesFileName);
_baseDirName = baseDirName;
_imageName = imageName;
_output = output;
Security.addProvider(new BouncyCastleProvider());
_provisioners = getProvisioners(properties);
}
protected void createImage() {
CreateImageRequest createImageRequest = new CreateImageRequest();
createImageRequest.setInstanceId(_instanceId);
createImageRequest.setName(_imageName);
CreateImageResult createImageResult = amazonEC2Client.createImage(
createImageRequest);
System.out.println("Creating image for instance " + _instanceId);
boolean created = false;
for (int i = 0; i < 6; i++) {
sleep(30);
created = isImageCreated(createImageResult.getImageId());
if (created) {
System.out.println(
"Created image " + createImageResult.getImageId());
break;
}
}
if (!created) {
System.out.println(
"Unable to create image " + createImageResult.getImageId());
deregisterImage(createImageResult.getImageId());
}
}
protected void deregisterImage(String imageId) {
DeregisterImageRequest deregisterImageRequest =
new DeregisterImageRequest();
deregisterImageRequest.setImageId(imageId);
amazonEC2Client.deregisterImage(deregisterImageRequest);
}
protected void destroy() {
if (_instanceId == null) {
return;
}
terminateInstance(_instanceId);
amazonEC2Client.shutdown();
_instanceId = null;
}
protected void executeScript(SSHClient sshClient, String scriptFileName)
throws Exception {
uploadFile(sshClient, scriptFileName, "/tmp");
File scriptFile = new File(scriptFileName);
scriptFileName = "/tmp/" + scriptFile.getName();
System.out.println("Executing script " + scriptFileName);
executeSessionCommand(
sshClient, "chmod +x " + scriptFileName + "; " + scriptFileName);
System.out.println("Deleting script " + scriptFileName);
executeSessionCommand(sshClient, "rm " + scriptFileName);
}
protected void executeSessionCommand(SSHClient sshClient, String command)
throws Exception {
System.out.println("Executing session command: " + command);
Session session = sshClient.startSession();
session.allocateDefaultPTY();
try {
Session.Command sessionCommand = session.exec(command);
if (_output) {
new StreamCopier(session.getInputStream(), System.out).copy();
}
sessionCommand.join();
}
finally {
session.close();
}
}
protected Instance getInstance(String instanceId, String state) {
DescribeInstancesRequest describeInstancesRequest =
new DescribeInstancesRequest();
List<Filter> filters = new ArrayList<>();
Filter filter = new Filter();
filter.setName("instance-state-name");
List<String> values = new ArrayList<>();
values.add(state);
filter.setValues(values);
filters.add(filter);
describeInstancesRequest.setFilters(filters);
List<String> instanceIds = new ArrayList<>();
instanceIds.add(instanceId);
describeInstancesRequest.setInstanceIds(instanceIds);
DescribeInstancesResult describeInstancesResult =
amazonEC2Client.describeInstances(describeInstancesRequest);
List<Reservation> reservations =
describeInstancesResult.getReservations();
if (reservations.isEmpty()) {
return null;
}
Reservation reservation = reservations.get(0);
List<Instance> instances = reservation.getInstances();
return instances.get(0);
}
protected String getKeyFileName() {
StringBuilder sb = new StringBuilder(6);
sb.append(System.getProperty("user.home"));
sb.append(File.separator);
sb.append(".ssh");
sb.append(File.separator);
sb.append(properties.getProperty("key.name"));
sb.append(".pem");
return sb.toString();
}
protected Map<String, String> getProvisioners(Properties properties) {
Map<String, String> provisioners = new TreeMap<>();
Set<String> names = properties.stringPropertyNames();
for (String name : names) {
if (!name.contains("provisioners")) {
continue;
}
String value = properties.getProperty(name);
provisioners.put(name, value);
}
return provisioners;
}
protected boolean isImageCreated(String imageId) {
DescribeImagesRequest describeImagesRequest =
new DescribeImagesRequest();
List<Filter> filters = new ArrayList<>();
Filter filter = new Filter();
filter.setName("state");
List<String> values = new ArrayList<>();
values.add("available");
filter.setValues(values);
filters.add(filter);
describeImagesRequest.setFilters(filters);
List<String> imageIds = new ArrayList<>();
imageIds.add(imageId);
describeImagesRequest.setImageIds(imageIds);
DescribeImagesResult describeImagesResult =
amazonEC2Client.describeImages(describeImagesRequest);
List<Image> images = describeImagesResult.getImages();
return !images.isEmpty();
}
protected boolean isZoneAvailable(String zoneName) {
DescribeAvailabilityZonesResult describeAvailabilityZonesResult =
amazonEC2Client.describeAvailabilityZones();
List<AvailabilityZone> availabilityZones =
describeAvailabilityZonesResult.getAvailabilityZones();
for (AvailabilityZone availabilityZone : availabilityZones) {
if (zoneName.equals(availabilityZone.getZoneName())) {
return true;
}
}
return false;
}
protected void provision() throws Exception {
System.out.println("Connecting via SSH to " + _publicIpAddress);
SSHClient sshClient = null;
for (int i = 0; i < 6; i++) {
sleep(30);
sshClient = new SSHClient();
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.setTimeout(
Integer.parseInt(properties.getProperty("ssh.timeout")) * 1000);
sshClient.useCompression();
try {
sshClient.connect(_publicIpAddress);
break;
}
catch (ConnectException ce) {
sshClient = null;
continue;
}
}
if (sshClient == null) {
throw new RuntimeException(
"Unable to connect via SSH to " + _publicIpAddress);
}
FileKeyProvider fileKeyProvider = new PKCS8KeyFile();
fileKeyProvider.init(new File(getKeyFileName()));
sshClient.authPublickey(
properties.getProperty("ssh.username"), fileKeyProvider);
try {
Set<String> keys = _provisioners.keySet();
for (String key : keys) {
String provisioner = _provisioners.get(key);
if (key.contains("shell.inline")) {
executeSessionCommand(sshClient, provisioner);
}
else if (key.contains("shell.script")) {
String shellScriptFileName = null;
if (provisioner.startsWith(File.separator)) {
shellScriptFileName = provisioner;
}
else {
shellScriptFileName =
_baseDirName + File.separator + provisioner;
}
executeScript(sshClient, shellScriptFileName);
}
else {
JSONObject jsonObject = new JSONObject(provisioner);
String fileName = null;
String src = jsonObject.getString("src");
if (src.startsWith(File.separator)) {
fileName = src;
}
else {
fileName =
_baseDirName + File.separator +
jsonObject.getString("src");
}
uploadFile(
sshClient, fileName, jsonObject.getString("dest"));
}
}
}
finally {
sshClient.disconnect();
}
}
protected void start() {
RunInstancesRequest runInstancesRequest = new RunInstancesRequest();
String availabilityZone = properties.getProperty("availability.zone");
if (!isZoneAvailable(availabilityZone)) {
throw new RuntimeException("Unavailable zone " + availabilityZone);
}
String imageId = properties.getProperty("image.id");
if (imageId == null) {
imageId = getImageId(properties.getProperty("image.name"));
}
runInstancesRequest.setImageId(imageId);
runInstancesRequest.setInstanceType(
properties.getProperty("instance.type"));
runInstancesRequest.setKeyName(properties.getProperty("key.name"));
runInstancesRequest.setMaxCount(1);
runInstancesRequest.setMinCount(1);
Placement placement = new Placement();
placement.setAvailabilityZone(availabilityZone);
runInstancesRequest.setPlacement(placement);
List<String> securityGroupsIds = new ArrayList<>();
securityGroupsIds.add(properties.getProperty("security.group.id"));
runInstancesRequest.setSecurityGroupIds(securityGroupsIds);
RunInstancesResult runInstancesResult = amazonEC2Client.runInstances(
runInstancesRequest);
Reservation reservation = runInstancesResult.getReservation();
List<Instance> instances = reservation.getInstances();
if (instances.isEmpty()) {
throw new RuntimeException("Unable to create instances");
}
Instance instance = instances.get(0);
_instanceId = instance.getInstanceId();
_publicIpAddress = instance.getPublicIpAddress();
StringBuilder sb = new StringBuilder(13);
sb.append("{imageId=");
sb.append(instance.getImageId());
sb.append(", instanceId=");
sb.append(_instanceId);
sb.append(", instanceType=");
sb.append(instance.getInstanceType());
sb.append(", keyName=");
sb.append(instance.getKeyName());
sb.append(", reservationId=");
sb.append(reservation.getReservationId());
sb.append(", state=");
InstanceState instanceState = instance.getState();
sb.append(instanceState.getName());
sb.append("}");
System.out.println("Starting instance " + sb.toString());
boolean running = false;
int i = 0;
do {
System.out.println("Waiting for running instance " + i + "...");
i = i + 1;
sleep(30);
instance = getInstance(_instanceId, "pending");
}
while (instance != null);
instance = getInstance(_instanceId, "running");
if (instance != null) {
_publicIpAddress = instance.getPublicIpAddress();
running = true;
sb = new StringBuilder(7);
sb.append("{instanceId=");
sb.append(_instanceId);
sb.append(", publicIpAddress=");
sb.append(_publicIpAddress);
sb.append(", stat=");
instanceState = instance.getState();
sb.append(instanceState.getName());
sb.append("}");
System.out.println("Started instance " + sb.toString());
}
if (!running) {
throw new RuntimeException(
"Unable to start instance " + _instanceId);
}
}
protected void terminateInstance(String instanceId) {
TerminateInstancesRequest terminateInstancesRequest =
new TerminateInstancesRequest();
List<String> instanceIds = terminateInstancesRequest.getInstanceIds();
instanceIds.add(instanceId);
TerminateInstancesResult terminateInstancesResult =
amazonEC2Client.terminateInstances(terminateInstancesRequest);
List<InstanceStateChange> instanceStateChanges =
terminateInstancesResult.getTerminatingInstances();
if (!instanceStateChanges.isEmpty()) {
System.out.println("Terminated instance " + instanceId);
}
else {
System.out.println("Unable to terminate instance " + instanceId);
}
}
protected void uploadFile(
SSHClient sshClient, String fileName, String destinationDir)
throws Exception {
System.out.println("Uploading file " + fileName);
SFTPClient sftpClient = sshClient.newSFTPClient();
try {
sftpClient.put(new FileSystemFile(fileName), destinationDir);
}
finally {
sftpClient.close();
}
}
private String _baseDirName;
private String _imageName;
private String _instanceId;
private boolean _output;
private Map<String, String> _provisioners;
private String _publicIpAddress;
}