fog_site 0.0.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/.document +5 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +63 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/fog_site.rb +157 -0
- data/test/helper.rb +18 -0
- data/test/test_fog_site.rb +7 -0
- metadata +137 -0
data/.document
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Brian P O'Rourke
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# fog_site #
|
2
|
+
|
3
|
+
*fog_site* is a simple utility gem for deploying a static site to Amazon S3 and
|
4
|
+
CloudFront using the *fog* gem.
|
5
|
+
|
6
|
+
To make deployments fast, this gem calculates the MD5 sum of every file locally
|
7
|
+
and compares them against the information stored on S3 (in the 'etag' property
|
8
|
+
of the S3 object). Files are only uploaded if the etag has changed.
|
9
|
+
|
10
|
+
## Usage ##
|
11
|
+
|
12
|
+
Create a new site
|
13
|
+
|
14
|
+
site = FogSite.new "www.stovepipeapp.com"
|
15
|
+
|
16
|
+
Decide what you want to deploy
|
17
|
+
|
18
|
+
site.path = "/home/bpo/stovepipe"
|
19
|
+
|
20
|
+
Decide whether to destroy resources that aren't local
|
21
|
+
|
22
|
+
site.destroy_old_files = true
|
23
|
+
|
24
|
+
If you're using CloudFront, specify a distribution ID:
|
25
|
+
|
26
|
+
site.distribution_id = "Z99120CAFEBABE"
|
27
|
+
|
28
|
+
And push it to the cloud
|
29
|
+
|
30
|
+
site.deploy!
|
31
|
+
|
32
|
+
All local settings for a site can be passed into the `new_site` method:
|
33
|
+
|
34
|
+
FogSite.new "stovepipeapp.com", :path => "/home/bpo/stovepipe",
|
35
|
+
:destroy_old_files => true
|
36
|
+
|
37
|
+
AWS credentials are read from the environment variables `AWSAccessKeyId` and
|
38
|
+
`AWSSecretKey`, but can also be set programmatically:
|
39
|
+
|
40
|
+
site = FogSite.new "stovepipeapp.com"
|
41
|
+
site.access_key_id = "E7L8CCM59BFXJOAMEAPT"
|
42
|
+
site.secret_key = "t5nszajv3y8u4zI/LtP/r5xUmPnqAT/Tv2n7Xr7n"
|
43
|
+
|
44
|
+
## Notes ##
|
45
|
+
|
46
|
+
### Choice of Domains ###
|
47
|
+
You can't point a CNAME to the apex of a zone, which means you can't use S3 +
|
48
|
+
CloudFront to deploy to 'foo.com', you can only deploy to a subdomain like
|
49
|
+
'www.foo.com'. If you want to have a URL like 'foo.com' work, you'll need to set
|
50
|
+
up a lightweight server that can accept requests for 'foo.com' and send a 301
|
51
|
+
permanent redirect to www.foo.com.
|
52
|
+
|
53
|
+
### Cloudfront Cache Invalidation ###
|
54
|
+
|
55
|
+
This gem uses cache invalidation to force new content to be refreshed by
|
56
|
+
CloudFront distributions. This works just fine, but if your content changes
|
57
|
+
often, it's better (and cheaper) to use versioning. See the CloudFront developer
|
58
|
+
guide for details on how to implement a versioning system.
|
59
|
+
|
60
|
+
## Copyright ##
|
61
|
+
|
62
|
+
Copyright (c) 2011 Brian P O'Rourke. See LICENSE.txt for
|
63
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "fog_site"
|
18
|
+
gem.homepage = "http://github.com/bpo/fog_site"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Deploys static sites to S3 using fog}
|
21
|
+
gem.description = %Q{Simple utility gem for deploying static sites to S3 and CloudFront using fog.}
|
22
|
+
gem.email = "bpo@somnambulance.net"
|
23
|
+
gem.authors = ["Brian P O'Rourke"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/fog_site.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require "colorize"
|
3
|
+
|
4
|
+
#
|
5
|
+
# A FogSite represents a site to be deployed to S3 and CloudFront. This object
|
6
|
+
# is a simple data structure, which is deployed with a `FogSite::Deployer`
|
7
|
+
#
|
8
|
+
class FogSite
|
9
|
+
attr_reader :domain_name
|
10
|
+
attr_writer :access_key_id, :secret_key
|
11
|
+
attr_accessor :path, :destroy_old_files, :distribution_id
|
12
|
+
|
13
|
+
def initialize( domain_name, attributes_map = {})
|
14
|
+
@domain_name = domain_name
|
15
|
+
attributes_map.each do |name, val|
|
16
|
+
setter = (name.to_s + "=").to_sym
|
17
|
+
self.send(setter, val)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def access_key_id
|
22
|
+
@access_key_id || ENV["AWSAccessKeyId"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def secret_key
|
26
|
+
@secret_key || ENV["AWSSecretKey"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def deploy!
|
30
|
+
Deployer.run(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Used to actually execute a deploy. This object is not safe for reuse - the
|
34
|
+
# `@index` and `@updated_paths` stay dirty after a deploy to allow debugging
|
35
|
+
# and inspection by client scripts.
|
36
|
+
class Deployer
|
37
|
+
attr_reader :index, :updated_paths
|
38
|
+
class UsageError < StandardError ; end
|
39
|
+
|
40
|
+
# Run a single deploy. Creates a new `Deployer` and calls `run`.
|
41
|
+
def self.run( site, options = {} )
|
42
|
+
deployer = Deployer.new( site )
|
43
|
+
deployer.run
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize( site )
|
47
|
+
@site = site
|
48
|
+
@index = {}
|
49
|
+
@updated_paths = []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Validate our `Site`, create a and configure a bucket, build the index,
|
53
|
+
# sync the files and (finally) invalidate all paths which have been updated
|
54
|
+
# on the content distribution network.
|
55
|
+
def run
|
56
|
+
validate
|
57
|
+
make_directory
|
58
|
+
Dir.chdir @site.path do
|
59
|
+
build_index
|
60
|
+
sync_remote
|
61
|
+
if( @site.distribution_id )
|
62
|
+
invalidate_cache(@site.distribution_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate
|
68
|
+
assert_not_nil @site.access_key_id, "No AccessKeyId specified"
|
69
|
+
assert_not_nil @site.secret_key, "No SecretKey specified"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates an S3 bucket for web site serving, using `index.html` and
|
73
|
+
# `404.html` as our special pages.
|
74
|
+
def make_directory
|
75
|
+
domain = @site.domain_name
|
76
|
+
puts "Using bucket: #{domain}".blue
|
77
|
+
@directory = connection.directories.create :key => domain,
|
78
|
+
:public => true
|
79
|
+
connection.put_bucket_website(domain, 'index.html', :key => "404.html")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Build an index of all the local files and their md5 sums. This will be
|
83
|
+
# used to decide what needs to be deployed.
|
84
|
+
def build_index
|
85
|
+
Dir["**/*"].each do |path|
|
86
|
+
unless File.directory?( path )
|
87
|
+
@index[path] = Digest::MD5.file(path).to_s
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Synchronize our local copy of the site with the remote one. This uses the
|
93
|
+
# index to detect what has been changed and upload only new/updated files.
|
94
|
+
# Helpful debugging information is emitted, and we're left with a populated
|
95
|
+
# `updated_paths` instance variable which can be used to invalidate cached
|
96
|
+
# content.
|
97
|
+
def sync_remote
|
98
|
+
@directory.files.each do |remote_file|
|
99
|
+
path = remote_file.key
|
100
|
+
local_file_md5 = @index[path]
|
101
|
+
|
102
|
+
if local_file_md5.nil? and @site.destroy_old_files
|
103
|
+
puts "#{path}: deleted".red
|
104
|
+
remote_file.destroy
|
105
|
+
elsif local_file_md5 == remote_file.etag
|
106
|
+
puts "#{path}: unchanged".white
|
107
|
+
@index.delete( path )
|
108
|
+
else
|
109
|
+
puts "#{path}: updated".green
|
110
|
+
write_file( path )
|
111
|
+
@index.delete( path )
|
112
|
+
@updated_paths << ("/" + path)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
@index.each do |path, md5|
|
117
|
+
puts "#{path}: new".green
|
118
|
+
write_file( path )
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Push a single file out to S3.
|
123
|
+
def write_file( path )
|
124
|
+
@directory.files.create :key => path,
|
125
|
+
:body => File.open( path ),
|
126
|
+
:public => true
|
127
|
+
end
|
128
|
+
|
129
|
+
# Compose and post a cache invalidation request to CloudFront. This will
|
130
|
+
# ensure that all CloudFront distributions get the latest content quickly.
|
131
|
+
def invalidate_cache( distribution_id )
|
132
|
+
unless @updated_paths.empty?
|
133
|
+
cdn.post_invalidation distribution_id, @updated_paths
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def cdn
|
138
|
+
@cdn ||= Fog::CDN.new( credentials )
|
139
|
+
end
|
140
|
+
|
141
|
+
def connection
|
142
|
+
@connection ||= Fog::Storage.new( credentials )
|
143
|
+
end
|
144
|
+
|
145
|
+
def credentials
|
146
|
+
{
|
147
|
+
:provider => 'AWS',
|
148
|
+
:aws_access_key_id => @site.access_key_id,
|
149
|
+
:aws_secret_access_key => @site.secret_key
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def assert_not_nil( value, error )
|
154
|
+
raise UsageError.new( error ) unless value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'fog_site'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fog_site
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brian P O'Rourke
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-22 00:00:00 +09:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 55
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 10
|
33
|
+
- 0
|
34
|
+
version: 0.10.0
|
35
|
+
name: fog
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
name: colorize
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :development
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 23
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 0
|
63
|
+
- 0
|
64
|
+
version: 1.0.0
|
65
|
+
name: bundler
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
prerelease: false
|
69
|
+
type: :development
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 7
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 6
|
79
|
+
- 4
|
80
|
+
version: 1.6.4
|
81
|
+
name: jeweler
|
82
|
+
version_requirements: *id004
|
83
|
+
description: Simple utility gem for deploying static sites to S3 and CloudFront using fog.
|
84
|
+
email: bpo@somnambulance.net
|
85
|
+
executables: []
|
86
|
+
|
87
|
+
extensions: []
|
88
|
+
|
89
|
+
extra_rdoc_files:
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
files:
|
93
|
+
- .document
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- VERSION
|
99
|
+
- lib/fog_site.rb
|
100
|
+
- test/helper.rb
|
101
|
+
- test/test_fog_site.rb
|
102
|
+
has_rdoc: true
|
103
|
+
homepage: http://github.com/bpo/fog_site
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 3
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
version: "0"
|
129
|
+
requirements: []
|
130
|
+
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 1.6.2
|
133
|
+
signing_key:
|
134
|
+
specification_version: 3
|
135
|
+
summary: Deploys static sites to S3 using fog
|
136
|
+
test_files: []
|
137
|
+
|