Jump To …

request.coffee

#

Require Node.js core modules.

crypto          = require("crypto")
http            = require("http")
querystring     = require("querystring")
#

Oh, how I miss you sprintf.

pad = (n) -> if n < 10 then "0" + n else n
#

Generate a UTC timestamp in ISO 8601 for the Amazon Query API request. The Amazon Query API will respond with an error if the timestamp of the request is not in the last twenty minutes or so.

timestamp = () ->
  now       = new Date()
  year      = now.getUTCFullYear()
  month     = pad(now.getUTCMonth() + 1)
  day       = pad(now.getUTCDate())
  hours     = pad(now.getUTCHours())
  minutes   = pad(now.getUTCMinutes())
  "#{year}-#{month}-#{day}T#{hours}:#{minutes}:00Z"

invoke = (key, secret, command, parameters, callback) ->
#

The parameters common to all Amazon Query API requests as documented in Making Query Requests in the Amazon Elastic Compute Cloud Developer Guide.

  map =
    AWSAccessKeyId: key
    Action: command
    SignatureMethod: "HmacSHA256"
    Timestamp: timestamp()
    SignatureVersion: 2
    Version: "2010-06-15"
#

Merge the request specific parameters with the common parameters.

  for key, value of parameters
    map[key] =
      if typeof parameters[key] == 'function'
        parameters[key]()
      else
        parameters[key]
#

The Amazon Query API authenticates the request with Calculate an RFC 2104-compliant HMAC. We build a string to sign that includes the query parameters ordered by the lexical order of the parameter names.

Here we sort the query parameter names and create an array of URL encoded query name value pairs.

  names = for key, value of map
    key
  names.sort()

  query = []
  for name in names
    query.push(querystring.escape(name) + "=" + querystring.escape(map[name]))
#

The string includes the request verb, server, request path and the query string sorted by the lexical order of the parameter names.

  toSign = "GET\n" +
    "ec2.amazonaws.com\n" +
    "/\n" +
    query.join("&")
#

Generate the HMAC.

  hmac = crypto.createHmac "sha256", process.env["AWS_SECRET_ACCESS_KEY"]
  hmac.update toSign
  digest = querystring.escape(hmac.digest("base64"))
#

Add the HMAC to the query string.

  query.push "Signature=" + digest
#

Connect to the Amazon Query API server, gather the response, and feed it to the callback function.

  client = http.createClient 443, "ec2.amazonaws.com", true
  headers = { host: "ec2.amazonaws.com" }
  request = client.request "GET", ("/?" + query.join("&")), headers
  request.end()
  request.on "response", (response) ->
    body = ""
    response.setEncoding "utf8"
    response.on "data", (chunk) ->
      body += chunk
    response.on "end", () ->
      callback response, body
  true

module.exports.invoke = invoke