Class: Swirl::AWS

Inherits:
Object
  • Object
show all
Includes:
Helpers::Compactor, Helpers::Expander
Defined in:
lib/swirl/aws.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::Expander

expand

Methods included from Helpers::Compactor

compact, compact!

Constructor Details

#initialize(*args) ⇒ AWS

Returns a new instance of AWS.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/swirl/aws.rb', line 38

def initialize(*args)
  opts = args.last.is_a?(Hash) ? args.pop : Hash.new
  name = args.shift

  service = self.class.services[name] || {}
  opts    = service.merge(opts)

  @url = URI(opts[:url]) ||
    raise(ArgumentError, "No url given")

  if region = opts[:region]
    parts = URI.split(@url.to_s)
    parts[2] = parts[2].split('.').insert(1, region).join('.')
    @url = URI::HTTPS.new(*parts)
  end

  @version = opts[:version] ||
    raise(ArgumentError, "No version given")

  @aws_access_key_id  = \
    opts[:aws_access_key_id] ||
    ENV["AWS_ACCESS_KEY_ID"] ||
    raise(ArgumentError, "No aws_access_key_id given")

  @aws_secret_access_key = \
    opts[:aws_secret_access_key] ||
    ENV["AWS_SECRET_ACCESS_KEY"] ||
    raise(ArgumentError, "No aws_secret_access_key given")

  @hmac = HMAC::SHA256.new(@aws_secret_access_key)
end

Class Method Details

.service(name, url, version) ⇒ Object



28
29
30
# File 'lib/swirl/aws.rb', line 28

def self.service(name, url, version)
  @services[name] = { :url => url, :version => version }
end

.servicesObject



23
24
25
26
# File 'lib/swirl/aws.rb', line 23

def self.services
  # This must be modified using `service`
  @services.dup
end

Instance Method Details

#call(action, query = {}, &blk) ⇒ Object

Execute an EC2 command, expand the input, and compact the output

Examples:

ec2.call("DescribeInstances")
ec2.call("TerminateInstances", "InstanceId" => ["i-1234", "i-993j"]


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/swirl/aws.rb', line 95

def call(action, query={}, &blk)
  call!(action, expand(query)) do |code, data|
    case code
    when 200
      response = compact(data)
    when 400...500
      messages = if data["Response"]
        Array(data["Response"]["Errors"]).map {|_, e| e["Message"] }
      elsif data["ErrorResponse"]
        Array(data["ErrorResponse"]["Error"]["Code"])
      end
      raise InvalidRequest, messages.join(",")
    else
      msg = "unexpected response #{code} -> #{data.inspect}"
      raise InvalidRequest, msg
    end

    if blk
      blk.call(response)
    else
      response
    end
  end
end

#call!(action, query = {}, &blk) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/swirl/aws.rb', line 120

def call!(action, query={}, &blk)
  log "Action: #{action}"
  log "Query:  #{query.inspect}"

  # Hard coding this here until otherwise needed
  method = "POST"

  query["Action"] = action
  query["AWSAccessKeyId"] = @aws_access_key_id
  query["SignatureMethod"] = "HmacSHA256"
  query["SignatureVersion"] = "2"
  query["Timestamp"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
  query["Version"] = @version

  body = compile_sorted_form_data(query)
  body += "&" + ["Signature", compile_signature(method, body)].join("=")

  post(body) do |code, xml|
    log "HTTP Response Code: #{code}"
    log xml.gsub("\n", "\n[swirl] ")

    data = Crack::XML.parse(xml)
    blk.call(code, data)
  end
end

#compile_signature(method, body) ⇒ Object



80
81
82
83
84
85
# File 'lib/swirl/aws.rb', line 80

def compile_signature(method, body)
  string_to_sign = [method, @url.host, "/", body] * "\n"
  hmac = @hmac.update(string_to_sign)
  encoded_sig = Base64.encode64(hmac.digest).chomp
  escape(encoded_sig)
end

#compile_sorted_form_data(query) ⇒ Object



75
76
77
78
# File 'lib/swirl/aws.rb', line 75

def compile_sorted_form_data(query)
  valid = query.reject {|_, v| v.nil? }
  valid.sort.map {|k,v| [k, escape(v)] * "=" } * "&"
end

#escape(value) ⇒ Object



71
72
73
# File 'lib/swirl/aws.rb', line 71

def escape(value)
  CGI.escape(value).gsub(/\+/, "%20")
end

#inspectObject



160
161
162
# File 'lib/swirl/aws.rb', line 160

def inspect
  "<#{self.class.name} version: #{@version} url: #{@url} aws_access_key_id: #{@aws_access_key_id}>"
end

#log(msg) ⇒ Object



164
165
166
167
168
# File 'lib/swirl/aws.rb', line 164

def log(msg)
  if ENV["SWIRL_LOG"]
    $stderr.puts "[swirl] #{msg}"
  end
end

#post(body, &blk) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/swirl/aws.rb', line 146

def post(body, &blk)
  headers = { "Content-Type" => "application/x-www-form-urlencoded" }

  http = Net::HTTP.new(@url.host, @url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE

  request = Net::HTTP::Post.new(@url.request_uri, headers)
  request.body = body

  response = http.request(request)
  blk.call(response.code.to_i, response.body)
end