Created
February 26, 2013 19:44
-
-
Save jroper/5041486 to your computer and use it in GitHub Desktop.
Barebones Ec2 client that uses the Play Framework WS API for provisioning instances. Handles authentication signing etc. If you want to use it, you probably want to read the docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-RunInstances.html and update it to do what you need it to do.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package utils | |
import scala.xml.Elem | |
import scala.concurrent.Future | |
import java.util.{TimeZone, Date} | |
import java.text.SimpleDateFormat | |
import java.net.URLEncoder | |
import javax.crypto.spec.SecretKeySpec | |
import javax.crypto.Mac | |
import org.apache.commons.codec.binary.Base64 | |
import play.api.libs.ws.WS | |
import play.api.libs.concurrent.Execution.Implicits._ | |
class Ec2Client(accessKeyId: String, accessKey: String, region: String) { | |
val version = "2012-12-01" | |
val endpoint = "ec2." + region + ".amazonaws.com" | |
val path = "/" | |
val method = "GET" | |
def provisionInstance(imageId: String, size: String, number: Int, userData: String): Future[Seq[String]] = { | |
new Ec2Request("RunInstances", Map( | |
"ImageId" -> imageId, | |
"MinCount" -> number.toString, | |
"MaxCount" -> number.toString, | |
"UserData" -> new String(Base64.encodeBase64(userData.getBytes("UTF-8"))), | |
"InstanceType" -> size, | |
"InstanceInitiatedShutdownBehavior" -> "terminate" | |
)).execute().map { xml => | |
(xml \\ "instanceId").toSeq.map(_.text) | |
} | |
} | |
def terminateInstance(instanceId: String) = { | |
new Ec2Request("TerminateInstances", Map( | |
"InstanceId.1" -> instanceId | |
)).execute() | |
} | |
private [utils] def signParams(method: String, endpoint: String, path: String, params: Map[String, String]) = { | |
// Format query params | |
val formattedQueryParams = params.map(param => param._1 + "=" + URLEncoder.encode(param._2, "UTF-8")).toList.sorted.mkString("&") | |
// Format the canonical query string | |
val canonical = s"${method}\n${endpoint}\n${path}\n${formattedQueryParams}" | |
// Sign it | |
val signingKey = new SecretKeySpec(accessKey.getBytes("UTF-8"), "HmacSHA256") | |
val mac = Mac.getInstance("HmacSHA256") | |
mac.init(signingKey) | |
val rawHmac = mac.doFinal(canonical.getBytes("UTF-8")) | |
new String(Base64.encodeBase64(rawHmac)) | |
} | |
private class Ec2Request(action: String, params: Map[String, String], method: String = this.method, endpoint: String = this.endpoint, path: String = this.path) { | |
def execute(): Future[Elem] = { | |
val url = WS.url("https://" + endpoint + path).withQueryString(calculateQueryParams.toSeq:_*) | |
(method match { | |
case "GET" => url.get() | |
case "POST" => url.post("") | |
}).map { resp => | |
if (resp.status < 300) { | |
resp.xml | |
} else { | |
println("Error calling endpoint " + resp.status + " " + resp.statusText + ": " + resp.body) | |
<error>{resp.status}: {resp.statusText}</error> | |
} | |
} recover { | |
case err => <error>{err.getMessage}</error> | |
} | |
} | |
def calculateQueryParams: Map[String, String] = { | |
val format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") | |
format.setTimeZone(TimeZone.getTimeZone("GMT")) | |
val timestamp = format.format(new Date()) | |
// Build up query params | |
val queryParams = params + | |
("Action" -> action) + | |
("Version" -> version) + | |
("AWSAccessKeyId" -> accessKeyId) + | |
("Timestamp" -> timestamp) + | |
("SignatureVersion" -> "2") + | |
("SignatureMethod" -> "HmacSHA256") | |
queryParams + ("Signature" -> signParams(method, endpoint, path, queryParams)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment