middleman-aws-deploy 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -0,0 +1,38 @@
1
+ # Middleman AWS Deploy
2
+
3
+ Deploys build files to S3 at the end of the Middleman build toolchain, and/or invalidates Cloudfront.
4
+
5
+ In your `config.rb` file, add.
6
+
7
+ ```
8
+ configure :build do
9
+ # ...
10
+ if ENV.include?('DEPLOY')
11
+ activate :s3_deploy do |s3|
12
+ s3.access_key_id = ENV['AWS_ACCESS_KEY_ID']
13
+ s3.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
14
+ s3.bucket = ENV['AWS_S3_BUCKET']
15
+ end
16
+ activate :invalidate_cloudfront do |cf|
17
+ cf.access_key_id = ENV['AWS_ACCESS_KEY_ID']
18
+ cf.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
19
+ cf.distribution_id = ENV['AWS_CLOUDFRONT_DIST_ID']
20
+ end
21
+ end
22
+ end
23
+ ```
24
+
25
+ With valid AWS creds, an s3 bucket, and cloudfront distribution id, you can deploy
26
+ with the env var DEPLOY as a trigger (you can use whichever kind of trigger you like):
27
+
28
+ ```
29
+ DEPLOY=true middleman build
30
+ ```
31
+
32
+ ### S3 Deployment Note
33
+
34
+ The S3 deployer will check local file's md5 hash against the remote s3 etag. If
35
+ they are the same, it will not upload the identical file. However, if
36
+ you have `:cachebuster` set to active the hashes will always end up different
37
+ on every build, triggering an upload. There's little such danger however for
38
+ assets (css, images, etc).
@@ -0,0 +1,6 @@
1
+ require "middleman-core"
2
+ require "middleman-aws-deploy/s3deploy"
3
+ require "middleman-aws-deploy/cloudfront-invalidate"
4
+
5
+ ::Middleman::Extensions.register(:s3_deploy, Middleman::AWSDeploy::S3Deploy)
6
+ ::Middleman::Extensions.register(:invalidate_cloudfront, Middleman::AWSDeploy::InvalidateCloudfront)
@@ -0,0 +1,98 @@
1
+ require 'rubygems'
2
+ require 'uri'
3
+ require 'hmac'
4
+ require 'hmac-sha1'
5
+ require 'net/https'
6
+ require 'base64'
7
+
8
+ CF_BATCH_SIZE = 1000
9
+
10
+ module Middleman
11
+ module AWSDeploy
12
+ module InvalidateCloudfront
13
+ Opts = Struct.new(:access_key_id, :secret_access_key, :distribution_id)
14
+
15
+ class << self
16
+ module Options
17
+ def aws_cfi_opts
18
+ ::Middleman::AWSDeploy::InvalidateCloudfront.options
19
+ end
20
+ end
21
+
22
+ def options
23
+ @@options
24
+ end
25
+
26
+ def registered(app, opts_data = {}, &block)
27
+ @@options = Opts.new(opts_data)
28
+ yield @@options if block_given?
29
+
30
+ app.send :include, Options
31
+ app.after_build do
32
+ puts "== Invalidating CloudFront"
33
+
34
+ # you can only invalidate in batches of 1000
35
+ def invalidate(files, pos=0)
36
+ paths, count = "", 0
37
+
38
+ escaped_files = []
39
+ files[pos...(pos+CF_BATCH_SIZE)].each do |key|
40
+ count += 1
41
+ escaped_files << (escaped_file = URI.escape(key.to_s))
42
+ paths += "<Path>#{escaped_file}</Path>"
43
+ end
44
+
45
+ return if count < 1
46
+
47
+ paths = "<Paths><Quantity>#{count}</Quantity><Items>#{paths}</Items></Paths>"
48
+
49
+ digest = HMAC::SHA1.new(aws_cfi_opts.secret_access_key)
50
+ digest << date = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S %Z")
51
+
52
+ uri = URI.parse("https://cloudfront.amazonaws.com/2012-07-01/distribution/#{aws_cfi_opts.distribution_id}/invalidation")
53
+ header = {
54
+ 'x-amz-date' => date,
55
+ 'Content-Type' => 'text/xml',
56
+ 'Authorization' => "AWS %s:%s" %
57
+ [aws_cfi_opts.access_key_id, Base64.encode64(digest.digest)]
58
+ }
59
+ req = Net::HTTP::Post.new(uri.path)
60
+ req.initialize_http_header(header)
61
+ body = "<InvalidationBatch xmlns=\"http://cloudfront.amazonaws.com/doc/2012-07-01/\">#{paths}<CallerReference>ref_#{Time.now.utc.to_i}</CallerReference></InvalidationBatch>"
62
+ req.body = body
63
+
64
+ http = Net::HTTP.new(uri.host, uri.port)
65
+ http.use_ssl = true
66
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
67
+ res = http.request(req)
68
+ if res.code == '201'
69
+ puts "CloudFront reloading #{count} paths"
70
+ else
71
+ $stderr.puts "CloudFront Invalidate failed with error #{res.code}"
72
+ $stderr.puts res.body
73
+ $stderr.puts escaped_files.join("\n")
74
+ end
75
+ return if res.code == 400
76
+
77
+ if count == CF_BATCH_SIZE
78
+ invalidate(files, pos+count)
79
+ end
80
+ end
81
+
82
+ def cf_files
83
+ files = Dir["./build/**/.*", "./build/**/*"]
84
+ .reject{|f| File.directory?(f) }.map{|f| f.sub(/^(?:\.\/)?build\//, '/') }
85
+ # if :directory_indexes is active, we must invalidate both files and dirs
86
+ # doing this 100% of the time, it would be nice if we could find of
87
+ files += files.map{|f| f.gsub(/\/index\.html$/, '/') }
88
+ files.uniq!
89
+ end
90
+
91
+ invalidate(cf_files)
92
+ end
93
+ end
94
+ alias :included :registered
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,98 @@
1
+ require 'rubygems'
2
+ require 'fog'
3
+ require 'parallel'
4
+ require 'progressbar'
5
+ require 'digest/md5'
6
+
7
+ module Middleman
8
+ module AWSDeploy
9
+ module S3Deploy
10
+ Opts = Struct.new(:access_key_id, :secret_access_key, :threads, :bucket, :region)
11
+
12
+ class << self
13
+ module Options
14
+ def aws_s3_opts
15
+ ::Middleman::AWSDeploy::S3Deploy.options
16
+ end
17
+ end
18
+
19
+ def options
20
+ @@options
21
+ end
22
+
23
+ def registered(app, opts_data = {}, &block)
24
+ @@options = Opts.new(opts_data)
25
+ yield @@options if block_given?
26
+
27
+ @@options.threads ||= 8
28
+ app.send :include, Options
29
+ app.after_build do
30
+ puts "== Uploading to S3"
31
+
32
+ def storage
33
+ @storage ||= Fog::Storage.new({
34
+ :provider => 'AWS',
35
+ :aws_access_key_id => aws_s3_opts.access_key_id,
36
+ :aws_secret_access_key => aws_s3_opts.secret_access_key,
37
+ :region => aws_s3_opts.region
38
+ })
39
+ end
40
+
41
+ def remote_files
42
+ @directory ||= storage.directories.get(aws_s3_opts.bucket)
43
+ @remote_files ||= @directory.files
44
+ end
45
+
46
+ def etag(key)
47
+ object = remote_files.head(key)
48
+ object && object.etag
49
+ end
50
+
51
+ def upload(key)
52
+ begin
53
+ remote_files.new({
54
+ :key => key,
55
+ :body => File.open("./build/#{key}"),
56
+ :public => true,
57
+ :acl => 'public-read'
58
+ }).save
59
+ rescue
60
+ $stderr.puts "Failed to upload #{key}"
61
+ end
62
+ end
63
+
64
+ def files
65
+ @files ||= Dir["./build/**/.*", "./build/**/*"]
66
+ .reject{|f| File.directory?(f) }.map{|f| f.sub(/^(?:\.\/)?build\//, '') }
67
+ end
68
+
69
+ # no need to upload an exact copy of an existing remote file
70
+ replace = []
71
+ progress = ProgressBar.new('Hash check', files.count)
72
+ Parallel.each(files,
73
+ :in_threads => aws_s3_opts.threads,
74
+ :finish => lambda {|i, item| progress.inc }) do |f|
75
+ md5 = Digest::MD5.hexdigest(File.read("./build/#{f}"))
76
+ replace << f if md5 != etag(f)
77
+ end
78
+ progress.finish
79
+
80
+ if replace.empty?
81
+ puts "Nothing to upload"
82
+ else
83
+ # upload necessary files
84
+ progress = ProgressBar.new('Uploading', replace.count)
85
+ Parallel.each(replace,
86
+ :in_threads => aws_s3_opts.threads,
87
+ :finish => lambda {|i, item| progress.inc }) do |f|
88
+ upload(f)
89
+ end
90
+ progress.finish
91
+ end
92
+ end
93
+ end
94
+ alias :included :registered
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,5 @@
1
+ module Middleman
2
+ module AWSDeploy
3
+ VERSION = "0.5.0"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "middleman-aws-deploy/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "middleman-aws-deploy"
7
+ s.version = Middleman::AWSDeploy::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Eric Redmond"]
10
+ s.email = ["eric.redmond@gmail.com"]
11
+ s.homepage = "https://github.com/coderoshi/middleman-aws-deploy"
12
+ s.summary = %q{Deploy to AWS with Middleman}
13
+ s.description = %q{Adds S3 deploy and Cloudfront invalidation support to the Middleman build toolchain}
14
+
15
+ s.rubyforge_project = "middleman-aws-deploy"
16
+
17
+ s.files = `git ls-files -z`.split("\0")
18
+ s.test_files = `git ls-files -z -- {fixtures,features}/*`.split("\0")
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency("middleman-core", ["~> 3.0.0"])
22
+ s.add_dependency("ruby-hmac", ["~> 0.4.0"])
23
+ s.add_dependency("parallel", ["~> 0.6.1"])
24
+ s.add_dependency("fog", ["~> 1.8.0"])
25
+ s.add_dependency("progressbar", ["~> 0.12.0"])
26
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: middleman-aws-deploy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eric Redmond
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: middleman-core
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: ruby-hmac
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.4.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.4.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: parallel
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.6.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.6.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: fog
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.8.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: progressbar
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.12.0
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.12.0
94
+ description: Adds S3 deploy and Cloudfront invalidation support to the Middleman build
95
+ toolchain
96
+ email:
97
+ - eric.redmond@gmail.com
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - Gemfile
103
+ - README.md
104
+ - lib/middleman-aws-deploy.rb
105
+ - lib/middleman-aws-deploy/cloudfront-invalidate.rb
106
+ - lib/middleman-aws-deploy/s3deploy.rb
107
+ - lib/middleman-aws-deploy/version.rb
108
+ - middleman-aws-deploy.gemspec
109
+ homepage: https://github.com/coderoshi/middleman-aws-deploy
110
+ licenses: []
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubyforge_project: middleman-aws-deploy
129
+ rubygems_version: 1.8.24
130
+ signing_key:
131
+ specification_version: 3
132
+ summary: Deploy to AWS with Middleman
133
+ test_files: []