shoots_deploy 0.9.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 79d13fd6c4fa3127ce86095f866d6d8324f9db76
4
+ data.tar.gz: 35d1408dc58021e7e1fe05ae81061f2949911336
5
+ SHA512:
6
+ metadata.gz: 3f3450e8b9bce9956015dd4736fe7b4a1e8628e0755f80f2b4db7fe614b037e4ce3822842c1afb34f4b001582e50ac419db8b3303baa5be38660de61f70ffb6d
7
+ data.tar.gz: efc64f14c2b23fe56bc443a042585b97bd871dc125ebecb64cd80b2de7659a5742d231f4024d48a3f21b57f6e1bfb36467188433b25a131b7fd95b7c5c1fae5d
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shoots_deploy.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josh Teng
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Shoots Deploy
2
+
3
+ ##Deploying websites the sucky old ways
4
+ The amount of work it takes to deploy static websites these days just isn't as fast and simple as it should be. You either use FTP or SCP or some paid hosted service such as the excellent `Brace.io`.
5
+
6
+ I personally love hosting my static websites on Amazon S3 because it's ridiculously cheap and flexible. It's not slow and it allows for multiple regions.
7
+
8
+ ##ShootsDeploy - The quick and simple way
9
+ This gem allows you to deploy static websites to your Amazon S3 by simply typing in command line in the root directory of your site.
10
+
11
+ ```
12
+ shoots
13
+ ```
14
+
15
+ Bam! In 30 seconds your site is live and/or updated!
16
+
17
+ Scenarios
18
+ - Deployed before √
19
+ - custom domain with r53 with root domain √
20
+ - custom domain with r53 with no root domain √
21
+ - custom domain with no r53 √
22
+ - no custom domain √
23
+
24
+ Edge cases not accounted for
25
+ - Bucket name taken
26
+
27
+ ##To do:
28
+ 1. Test `http://www.smashingmagazine.com/2014/04/08/how-to-build-a-ruby-gem-with-bundler-test-driven-development-travis-ci-and-coveralls-oh-my/`
29
+ 2. Documentation `http://guides.rubygems.org/make-your-own-gem/#documenting-code`
30
+ 3. Refactor
31
+ 4. Rename to `ShootDeploy`?
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/shoots ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shoots_deploy'
4
+ ShootsDeploy.deploy
@@ -0,0 +1,69 @@
1
+ require 'aws-sdk'
2
+
3
+ class ShootsDeploy::Bucket
4
+ attr_accessor :configuration
5
+
6
+ def initialize(bucket_name)
7
+ @name = bucket_name
8
+ s3 = AWS::S3.new
9
+ @s3_bucket = s3.buckets[bucket_name]
10
+ end
11
+
12
+ def self.initialize_with_name(bucket_name)
13
+ puts "\nCreating bucket with name #{bucket_name}"
14
+ s3 = AWS::S3.new
15
+ s3.buckets.create(bucket_name)
16
+ new(bucket_name)
17
+ end
18
+
19
+ def configure_policy
20
+ @s3_bucket.policy = AWS::S3::Policy.from_json("{\"Version\":\"2008-10-17\",\"Id\":\"d1b38dd800704a01924bef9a0b40f05f\",\"Statement\":[{\"Sid\":\"Allow Public Access to All Objects\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::#{@name}/*\"],\"Action\":[\"s3:GetObject\"]}]}")
21
+ end
22
+
23
+ def configure_to_serve_website
24
+ print "\nWhat is your index document? eg. index.html or home.html: "
25
+ index_document = gets.chomp
26
+ index_document = index_document.empty? ? 'index.html' : index_document
27
+
28
+ print "\nWhat is your error document? eg. 404.html or error.html: "
29
+ error_document = gets.chomp
30
+ error_document = error_document.empty? ? '404.html' : error_document
31
+
32
+ @s3_bucket.configure_website do |cfg|
33
+ cfg.index_document_suffix = index_document
34
+ cfg.error_document_key = error_document
35
+ end
36
+ end
37
+
38
+ #for secondary buckets
39
+ def redirect_traffic
40
+ puts "\nRedirecting secondary bucket's traffic to #{configuration.root_domain}"
41
+ @s3_bucket.website_configuration = AWS::S3::WebsiteConfiguration.new(redirect_all_requests_to: { host_name: configuration.root_domain })
42
+ end
43
+
44
+ def upload_files_from(folder_absolute_path)
45
+ puts "\nUploading all files in #{folder_absolute_path}"
46
+ Dir.glob(folder_absolute_path + '/**/**') do |file|
47
+ next if File.directory?(file)
48
+ configuration_file_regex = "#{ShootsDeploy::Configuration::CONFIG_FILE}"
49
+ configuration_file_regex = Regexp.new(configuration_file_regex)
50
+ next if file.match(configuration_file_regex)
51
+
52
+ folder_absolute_path_regex = folder_absolute_path + "/"
53
+ file_relative_path = file.gsub(folder_absolute_path_regex, '')
54
+ puts "Uploading #{file_relative_path}....."
55
+ obj = @s3_bucket.objects[file_relative_path]
56
+ obj.write(Pathname.new(file))
57
+ end
58
+
59
+ puts "\nFinish uploading!"
60
+ end
61
+
62
+ def sync_site_with(folder_absolute_path)
63
+ puts "\nDeleting old files..."
64
+ @s3_bucket.clear!
65
+ puts "\nUploading new files..."
66
+ upload_files_from(folder_absolute_path)
67
+ puts "\nSite updated!"
68
+ end
69
+ end
@@ -0,0 +1,33 @@
1
+ require 'yaml'
2
+
3
+ class ShootsDeploy::Configuration
4
+ attr_accessor :main_bucket_name, :secondary_bucket_name, :region, :root_domain, :subdomain, :subdomain_url, :secret_key, :access_key
5
+
6
+ CONFIG_FILE = "shoots.yml"
7
+
8
+ def initialize(options = {})
9
+ @main_bucket_name = options[:main_bucket_name]
10
+ @region = options[:region]
11
+ @secret_key = options[:secret_key]
12
+ @access_key = options[:access_key]
13
+ end
14
+
15
+ def self.initialize_from_file
16
+ configs = YAML.load_file(CONFIG_FILE)
17
+ new({
18
+ main_bucket_name: configs['bucket_name'],
19
+ region: configs['bucket_region'],
20
+ secret_key: configs['secret_key'],
21
+ access_key: configs['access_key']
22
+ })
23
+ end
24
+
25
+ def create_config_file
26
+ config_file = File.new(CONFIG_FILE, "w")
27
+ config_file.puts("bucket_name: '#{main_bucket_name}'\nbucket_region: '#{region}'\naccess_key: '#{access_key}'\nsecret_key: '#{secret_key}'\n")
28
+ end
29
+
30
+ def s3_website_endpoint
31
+ "#{main_bucket_name}.s3-website-#{region}.amazonaws.com"
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ require 'aws-sdk'
2
+
3
+ class ShootsDeploy::HostedZone
4
+ attr_accessor :configuration
5
+
6
+ def initialize(configuration)
7
+ r53 = AWS::Route53.new
8
+ @configuration = configuration
9
+ @hosted_zone = r53.hosted_zones.create(configuration.root_domain)
10
+ end
11
+
12
+ def configure_alias_record
13
+ @hosted_zone.rrsets.create(configuration.root_domain, 'A', alias_target: {
14
+ dns_name: "s3-website-#{configuration.region}.amazonaws.com",
15
+ evaluate_target_health: false,
16
+ hosted_zone_id: AWS::Route53::HostedZone::S3_HOSTED_ZONE_IDS[configuration.region]
17
+ })
18
+ end
19
+
20
+ def configure_cname_record
21
+ @hosted_zone.rrsets.create(configuration.subdomain_url, 'CNAME', :ttl => 300, :resource_records => [{:value => "#{configuration.subdomain_url}.s3-website-#{configuration.region}.amazonaws.com"}])
22
+
23
+ end
24
+
25
+ def transfer_existing_dns_settings
26
+ puts "pending implementation"
27
+ end
28
+
29
+ def ns_resource_records
30
+ @hosted_zone.resource_record_sets.to_a.reject { |r| r.type != 'NS' }[0].resource_records.map { |v| v[:value].gsub(/[.]$/, '') }
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module ShootsDeploy
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,188 @@
1
+ require "shoots_deploy/version"
2
+ require "shoots_deploy/configuration"
3
+ require "shoots_deploy/hosted_zone"
4
+ require "shoots_deploy/bucket"
5
+
6
+ module ShootsDeploy
7
+ def self.deploy
8
+ if deployed_before?
9
+ configuration = Configuration.initialize_from_file
10
+ initialize_aws(region: configuration.region, secret_key: configuration.secret_key, access_key: configuration.access_key)
11
+ bucket = Bucket.new(configuration.main_bucket_name)
12
+ bucket.sync_site_with(Dir.pwd)
13
+ else
14
+ access_key = get_access_key
15
+ secret_key = get_secret_key
16
+ region = select_region
17
+
18
+ configuration = Configuration.new(region: region, access_key: access_key, secret_key: secret_key)
19
+
20
+ initialize_aws(region: configuration.region, secret_key: configuration.secret_key, access_key: configuration.access_key)
21
+
22
+ if use_custom_domain? && use_route_53?
23
+ configuration.root_domain = get_root_domain
24
+ configuration.subdomain = get_subdomain
25
+ hosted_zone = HostedZone.new(configuration)
26
+
27
+ if use_root_domain_with_route_53?(configuration) #this also makes the root domain as the primary url to the site
28
+ configuration.main_bucket_name = configuration.root_domain
29
+ configuration.secondary_bucket_name = configuration.subdomain + '.' +configuration.root_domain
30
+ configuration.subdomain_url = configuration.secondary_bucket_name
31
+
32
+ create_configure_populate_main_bucket(configuration)
33
+
34
+ secondary_bucket = Bucket.initialize_with_name(configuration.secondary_bucket_name)
35
+ secondary_bucket.configuration = configuration
36
+ secondary_bucket.redirect_traffic
37
+
38
+ hosted_zone.configure_alias_record
39
+ hosted_zone.configure_cname_record
40
+
41
+ else #no root domain
42
+ configuration.main_bucket_name = configuration.subdomain + '.' + configuration.root_domain
43
+ configuration.subdomain_url = configuration.main_bucket_name
44
+
45
+ create_configure_populate_main_bucket(configuration)
46
+
47
+ hosted_zone.configure_cname_record
48
+ end
49
+
50
+ #prompt user to set up Route 53 as their DNS
51
+ puts "\nSet up your domain to use the Amazon Route 53 as your DNS by changing to the following nameservers:"
52
+ puts hosted_zone.ns_resource_records
53
+
54
+ notify_user_of_temporary_s3_url(configuration)
55
+
56
+ elsif use_custom_domain? #no Amazon Route 53, own DNS like cloudflare
57
+ configuration.subdomain_url = configuration.main_bucket_name = get_url
58
+
59
+ create_configure_populate_main_bucket(configuration)
60
+
61
+ #prompt user to set up DNS server
62
+ puts "\nSet up your DNS with the corresponding CNAME and point it to `#{configuration.s3_website_endpoint}`.\nIf you want your root domain to be redirected to this website, set up the necessary redirection rules."
63
+
64
+ notify_user_of_temporary_s3_url(configuration)
65
+
66
+ else #no custom domain
67
+ configuration.main_bucket_name = get_site_name
68
+
69
+ create_configure_populate_main_bucket(configuration)
70
+
71
+ puts "\nYou can see your website at #{configuration.s3_website_endpoint}"
72
+ end
73
+ configuration.create_config_file
74
+ end
75
+ end
76
+
77
+ #ancilliary methods
78
+ def self.deployed_before?
79
+ File.exist?(Dir.pwd + '/' + Configuration::CONFIG_FILE)
80
+ end
81
+
82
+ def self.initialize_aws(region: nil, access_key: nil, secret_key: nil)
83
+ AWS.config(access_key_id: access_key, secret_access_key: secret_key, region: region)
84
+ end
85
+
86
+ def self.create_configure_populate_main_bucket(configuration, directory: Dir.pwd)
87
+ main_bucket = Bucket.initialize_with_name(configuration.main_bucket_name)
88
+ main_bucket.upload_files_from(directory)
89
+ main_bucket.configure_policy
90
+ main_bucket.configure_to_serve_website
91
+ end
92
+
93
+ def self.select_region
94
+ while true
95
+ puts "\nSelect your region eg."
96
+ AWS.regions.to_a.each_with_index { |r, i| puts "#{i+1}. #{r.name}" }
97
+ print "\nType the number of the region: "
98
+ region_index = gets.chomp.to_i - 1
99
+ selected_region = AWS.regions.map { |r| r.name }[region_index]
100
+ print "\nYou have selected #{selected_region}.\nIs that right? (y/n): "
101
+ confirmation = gets.chomp
102
+ break selected_region if confirmation == 'y' #must have break because while loop returns nil with a break it returns selected_region
103
+ end
104
+ end
105
+
106
+ def self.get_access_key
107
+ print "\nWhat's your AWS access key? "
108
+
109
+ while access_key.empty?
110
+ access_key = gets.chomp
111
+ end
112
+
113
+ access_key
114
+ end
115
+
116
+ def self.get_secret_key
117
+ print "\nWhat's your AWS secret key? "
118
+
119
+ while secret_key.empty?
120
+ secret_key = gets.chomp
121
+ end
122
+
123
+ secret_key
124
+ end
125
+
126
+ def self.use_custom_domain?
127
+ @custom_domain = @custom_domain || get_user_confirmation("Do you want to use a custom domain for your site? (type y/n)")
128
+ @custom_domain == 'y'
129
+ end
130
+
131
+ def self.use_route_53?
132
+ use_route_53 = get_user_confirmation("Do you want to use AWS Route 53 as your DNS provider? (type y/n)")
133
+ use_route_53 == 'y'
134
+ end
135
+
136
+ def self.use_root_domain_with_route_53?(configuration)
137
+ use_root_domain = get_user_confirmation("Do you want to use your root domain, #{configuration.root_domain} for this site? (type y/n)")
138
+ use_root_domain == 'y'
139
+ end
140
+
141
+ def self.get_root_domain
142
+ prompt = "What is your root domain? eg. example.com"
143
+ confirmation_message = "You root domain is"
144
+ get_user_input_with_prompt(prompt, confirmation_message: confirmation_message)
145
+ end
146
+
147
+ def self.get_subdomain
148
+ prompt = "What is your preferred subdomain? eg. www or hello"
149
+ confirmation_message = "Your preferred subdomain is"
150
+ get_user_input_with_prompt(prompt, confirmation_message: confirmation_message)
151
+ end
152
+
153
+ def self.get_url
154
+ prompt = "What will your website URL be? eg. www.example.com"
155
+ confirmation_message = "Your website's url will be"
156
+ get_user_input_with_prompt(prompt, confirmation_message: confirmation_message)
157
+ end
158
+
159
+ def self.get_site_name
160
+ prompt = "What is the name of your site? (something unique to prevent S3 bucket name clashing)"
161
+ confirmation_message = "Your site name is"
162
+ get_user_input_with_prompt(prompt, confirmation_message: confirmation_message)
163
+ end
164
+
165
+ def self.get_user_input_with_prompt(prompt, confirmation_message: "You entered")
166
+ while true
167
+ print "\n#{prompt}: "
168
+ input = gets.chomp
169
+ print "\n#{confirmation_message} #{input}. Is that right? (y/n): "
170
+ confirmation = gets.chomp
171
+ break input if confirmation == 'y'
172
+ end
173
+ end
174
+
175
+ def self.get_user_confirmation(prompt)
176
+ while true
177
+ print "\n#{prompt}: "
178
+ input = gets.chomp
179
+ print "\nAre you sure? (y/n): "
180
+ confirmation = gets.chomp
181
+ break input if confirmation == 'y'
182
+ end
183
+ end
184
+
185
+ def self.notify_user_of_temporary_s3_url(configuration)
186
+ puts "\nIn the meantime, you can see your website at #{configuration.s3_website_endpoint}"
187
+ end
188
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'shoots_deploy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "shoots_deploy"
8
+ spec.version = ShootsDeploy::VERSION
9
+ spec.authors = ["Josh Teng"]
10
+ spec.email = ["joshteng@webermize.com"]
11
+ spec.summary = %q{Deploy static websites to Amazon S3}
12
+ spec.description = %q{Deploy your website to Amazon S3 within seconds easily and simply from your command line.}
13
+ spec.homepage = "http://joshteng.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency 'aws-sdk', '~> 1.50.0'
25
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shoots_deploy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Teng
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.50.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.50.0
55
+ description: Deploy your website to Amazon S3 within seconds easily and simply from
56
+ your command line.
57
+ email:
58
+ - joshteng@webermize.com
59
+ executables:
60
+ - shoots
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".gitignore"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/shoots
70
+ - lib/shoots_deploy.rb
71
+ - lib/shoots_deploy/bucket.rb
72
+ - lib/shoots_deploy/configuration.rb
73
+ - lib/shoots_deploy/hosted_zone.rb
74
+ - lib/shoots_deploy/version.rb
75
+ - shoots_deploy.gemspec
76
+ homepage: http://joshteng.com
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.4.5
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Deploy static websites to Amazon S3
100
+ test_files: []