wad 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #!/usr/bin/env ruby
  2. # Generated on: 20-09-2013 at 12:38
  3. require 'time'
  4. require 'net/http'
  5. require 'net/https'
  6. require 'digest/md5'
  7. require 'digest/sha1'
  8. require 'fileutils'
  9. require 'openssl'
  10. require 'base64'
  11. require 'cgi'
  12. class Presss
  13. # Computes the Authorization header for a AWS request based on a message,
  14. # the access key ID and secret access key.
  15. class Authorization
  16. attr_accessor :access_key_id, :secret_access_key
  17. def initialize(access_key_id, secret_access_key)
  18. @access_key_id, @secret_access_key = access_key_id, secret_access_key
  19. end
  20. # Returns the value for the Authorization header for a message contents.
  21. def header(string)
  22. 'AWS ' + access_key_id + ':' + sign(string)
  23. end
  24. # Returns a signature for a AWS request message.
  25. def sign(string)
  26. Base64.encode64(hmac_sha1(string)).strip
  27. end
  28. def hmac_sha1(string)
  29. OpenSSL::HMAC.digest('sha1', secret_access_key, string)
  30. end
  31. end
  32. class HTTP
  33. attr_accessor :config
  34. def initialize(config)
  35. @config = config
  36. end
  37. # Returns the configured bucket name.
  38. def bucket_name
  39. config[:bucket_name]
  40. end
  41. def region
  42. config[:region] || 'us-east-1'
  43. end
  44. def domain
  45. case region
  46. when 'us-east-1'
  47. 's3.amazonaws.com'
  48. else
  49. 's3-%s.amazonaws.com' % region
  50. end
  51. end
  52. def bucket_in_hostname?
  53. config[:bucket_in_hostname]
  54. end
  55. def url_prefix
  56. if bucket_in_hostname?
  57. "https://#{bucket_name}.#{domain}"
  58. else
  59. "https://#{domain}/#{bucket_name}"
  60. end
  61. end
  62. # Returns the absolute path based on the key for the object.
  63. def absolute_path(path)
  64. path.start_with?('/') ? path : '/' + path
  65. end
  66. # Returns the canonicalized resource used in the authorization
  67. # signature for an absolute path to an object.
  68. def canonicalized_resource(path)
  69. if bucket_name.nil?
  70. raise ArgumentError, "Please configure a bucket_name: Presss.config = { bucket_name: 'my-bucket-name }"
  71. else
  72. '/' + bucket_name + absolute_path(path)
  73. end
  74. end
  75. # Returns a Presss::Authorization instance for the configured
  76. # AWS credentials.
  77. def authorization
  78. @authorization ||= Presss::Authorization.new(
  79. config[:access_key_id],
  80. config[:secret_access_key]
  81. )
  82. end
  83. def signed_url(verb, expires, headers, path)
  84. path = absolute_path(path)
  85. canonical_path = canonicalized_resource(path)
  86. signature = [ verb.to_s.upcase, nil, nil, expires, [ headers, canonical_path ].flatten.compact ].flatten.join("\n")
  87. signed = authorization.sign(signature)
  88. "#{url_prefix}#{path}?Signature=#{CGI.escape(signed)}&Expires=#{expires}&AWSAccessKeyId=#{CGI.escape(authorization.access_key_id)}"
  89. end
  90. def download(path, destination)
  91. url = signed_url(:get, Time.now.to_i + 600, nil, path)
  92. Presss.log "signed_url=#{url}"
  93. system 'curl', '-f', '-S', '-o', destination, url
  94. $?.success?
  95. end
  96. # Puts an object with a key using a file or string. Optionally pass in
  97. # the content-type if you want to set a specific one.
  98. def put(path, file)
  99. header = 'x-amz-storage-class:REDUCED_REDUNDANCY'
  100. url = signed_url(:put, Time.now.to_i + 600, header, path)
  101. Presss.log "signed_url=#{url}"
  102. system 'curl', '-f', '-S', '-H', header, '-T', file, url
  103. $?.success?
  104. end
  105. end
  106. class << self
  107. attr_accessor :config
  108. attr_accessor :logger
  109. end
  110. self.config = {}
  111. # Get a object with a certain key.
  112. def self.download(path, destination)
  113. t0 = Time.now
  114. request = Presss::HTTP.new(config)
  115. log("Trying to GET #{path}")
  116. if request.download(path, destination)
  117. puts("[wad] Downloaded in #{(Time.now - t0).to_i} seconds")
  118. true
  119. else
  120. nil
  121. end
  122. end
  123. # Puts an object with a key using a file or string. Optionally pass in
  124. # the content-type if you want to set a specific one.
  125. def self.put(path, filename, content_type='application/x-download')
  126. request = Presss::HTTP.new(config)
  127. log("Trying to PUT #{path}")
  128. request.put(path, filename)
  129. end
  130. # Logs to the configured logger if a logger was configured.
  131. def self.log(message)
  132. if logger
  133. logger.info('[Presss] ' + message)
  134. end
  135. end
  136. end
  137. # Utility class to push and fetch Bundler directories to speed up
  138. # test runs on Travis-CI
  139. class Wad
  140. class Key
  141. def default_environment_variables
  142. []
  143. end
  144. def default_files
  145. [ "#{ENV['BUNDLE_GEMFILE']}.lock" ]
  146. end
  147. def environment_variables
  148. if ENV['WAD_ENVIRONMENT_VARIABLES']
  149. ENV['WAD_ENVIRONMENT_VARIABLES'].split(',')
  150. else
  151. default_environment_variables
  152. end
  153. end
  154. def files
  155. ENV['WAD_FILES'] ? ENV['WAD_FILES'].split(',') : default_files
  156. end
  157. def environment_variable_contents
  158. environment_variables.map { |v| ENV[v] }
  159. end
  160. def file_contents
  161. files.map { |f| File.read(f) rescue nil }
  162. end
  163. def contents
  164. segments = [ RUBY_VERSION, RUBY_PLATFORM ] + environment_variable_contents + file_contents
  165. Digest::SHA1.hexdigest(segments.join("\n"))
  166. end
  167. end
  168. def initialize
  169. s3_configure
  170. end
  171. def project_root
  172. Dir.pwd
  173. end
  174. def artifact_name
  175. @artifact_name ||= Key.new.contents
  176. end
  177. def bzip_filename
  178. File.join(project_root, "tmp/#{artifact_name}.tar.bz2")
  179. end
  180. def cache_path
  181. ENV['WAD_CACHE_PATH'] ? ENV['WAD_CACHE_PATH'].split(",") : [ '.bundle' ]
  182. end
  183. def s3_bucket_name
  184. if bucket = ENV['WAD_S3_BUCKET_NAME'] || ENV['S3_BUCKET_NAME']
  185. bucket
  186. end
  187. end
  188. def s3_credentials
  189. if creds = ENV['WAD_S3_CREDENTIALS'] || ENV['S3_CREDENTIALS']
  190. creds.split(':')
  191. end
  192. end
  193. def s3_access_key_id
  194. s3_credentials && s3_credentials[0]
  195. end
  196. def s3_secret_access_key
  197. s3_credentials && s3_credentials[1]
  198. end
  199. def s3_path
  200. "#{artifact_name}.tar.bz2"
  201. end
  202. def s3_configure
  203. Presss.config = {
  204. :bucket_name => s3_bucket_name,
  205. :access_key_id => s3_access_key_id,
  206. :secret_access_key => s3_secret_access_key,
  207. :region => ENV['WAD_AWS_REGION'],
  208. :bucket_in_hostname => (ENV['WAD_BUCKET_IN_HOSTNAME'] == 'true')
  209. }
  210. end
  211. def s3_write
  212. log "Trying to write Wad to S3"
  213. if Presss.put(s3_path, bzip_filename)
  214. log "Wrote Wad to S3"
  215. else
  216. log "Failed to write to S3, debug with `wad -v'"
  217. end
  218. end
  219. def s3_read
  220. if File.exist?(bzip_filename)
  221. log "Removing bundle from filesystem"
  222. FileUtils.rm_f(bzip_filename)
  223. end
  224. log "Trying to fetch Wad from S3"
  225. FileUtils.mkdir_p(File.dirname(bzip_filename))
  226. Presss.download(s3_path, bzip_filename)
  227. end
  228. def zip
  229. log "Creating artifact with tar (#{File.basename(bzip_filename)})"
  230. system("cd #{project_root} && tar -cPjf #{bzip_filename} #{cache_path.join(' ')}")
  231. $?.success?
  232. end
  233. def unzip
  234. log "Unpacking artifact with tar (#{File.basename(bzip_filename)})"
  235. system("cd #{project_root} && tar -xPjf #{bzip_filename}")
  236. $?.success?
  237. end
  238. def put
  239. zip
  240. s3_write
  241. end
  242. def get
  243. if s3_read
  244. unzip
  245. end
  246. end
  247. def default_command
  248. bundle_without = ENV['WAD_BUNDLE_WITHOUT'] || "development production"
  249. "bundle install --path .bundle --without='#{bundle_without}'"
  250. end
  251. def install
  252. log "Installing..."
  253. command = ENV['WAD_INSTALL_COMMAND'] || default_command
  254. puts command
  255. system(command)
  256. $?.success?
  257. end
  258. def setup
  259. if !s3_credentials || !s3_bucket_name
  260. log "No S3 credentials defined. Set WAD_S3_CREDENTIALS= and WAD_S3_BUCKET_NAME= for caching."
  261. install
  262. elsif get
  263. install
  264. elsif install
  265. put
  266. else
  267. abort "Failed properly fetch or install. Please review the logs."
  268. end
  269. end
  270. def log(message)
  271. puts "[wad] #{message}"
  272. end
  273. end
  274. if ARGV.index('-v')
  275. require 'logger'
  276. Presss.logger = Logger.new($stdout)
  277. end
  278. Wad.new.setup
  279. __END__