middleman-aws-deploy 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []