foghorn 0.0.3

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDVlMjM2NGIwMTRkNjBkMDgwOWU2MDQ5MWE4MTRlNTQ4Mjc1NDRiYg==
5
+ data.tar.gz: !binary |-
6
+ NTAxNzE0OTM2ZGVkMzIwZjMzNzMwMzg0ZmRiNGJkMGJmY2MwY2E1Nw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NWM5ZGZjOThjMzk2MTIzODNmYmI3OThjN2E5NzI5MGYwMzA0ZjI4NzQxYmZk
10
+ YjdhOGYyZGQyNWNjMDlkYzFjNGJhZDJlYTM3ZWFhODhhMDAwNjE4N2IxYTEx
11
+ MjM4MmY1YzkyNGI1YWE5OWM3NzE5NmVlODI4NmRmN2JjYWJiZDI=
12
+ data.tar.gz: !binary |-
13
+ ZmQ4Y2FhZDcxNTM1YTcyYTNhMmM2NmRiNmNiZDNjOGIyN2IwNmFjY2E1ZTQ1
14
+ MjE1MTA1ODEzYWIxYTk4Y2RhNmViMWRmNjU3YTBiNDQ0YWFjNTFkMTk0NzIy
15
+ MjdhOWFhZjM0YzI2NzhhMTYyNDQ5MGJhZjlmYWNiZWNhNWQ4ZWQ=
@@ -0,0 +1,19 @@
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
+
19
+ *~
@@ -0,0 +1,10 @@
1
+ AllCops:
2
+ Include:
3
+ - /Rakefile
4
+ - config ru
5
+ Exclude:
6
+ - *.gemspec
7
+ MethodLength:
8
+ Max: 25
9
+ LineLength:
10
+ Max: 78
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in foghorn.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dolphin Micro
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.
@@ -0,0 +1,31 @@
1
+ # Foghorn
2
+
3
+ Deploy to the cloud without specifying specific servers
4
+
5
+ ## Requirements
6
+
7
+ Currently only works with Capistrano 2.15
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'foghorn', '0.0.3', git: 'git@bitbucket.org:dolphinmicro/foghorn.git'
14
+
15
+ Require Foghorn in your deploy.rb
16
+ # this must go before loading stages!
17
+ set :foghorn_config,
18
+ provider: 'AWS',
19
+ aws_access_key_id: 'rotpacxullos',
20
+ aws_secret_access_key: '12345',
21
+ region: 'us-west-2'
22
+ require 'foghorn/capistrano'
23
+
24
+ ## Usage
25
+
26
+ In your individual stages, you can now set roles based on the Roles tag in the cloud:
27
+ role_by_tag :app, :staging_app
28
+ role_by_tag :db, :staging_db
29
+
30
+ You can test your configuration with foghorn:blow
31
+ bundle exec cap production foghorn:blow
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'foghorn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'foghorn'
8
+ spec.version = Foghorn::VERSION
9
+ spec.authors = ['Chris Dollard']
10
+ spec.email = ['chrisdollard@dolphinmicro.com']
11
+ spec.description = %q(Deploy to the cloud)
12
+ spec.summary = %q(Tag roles in EC2, not in your deploy scripts)
13
+ spec.homepage = 'https://bitbucket.org/dolphinmicro/foghorn'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
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.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rb-readline'
24
+ spec.add_development_dependency 'pry'
25
+ spec.add_development_dependency 'pry_debug'
26
+ spec.add_development_dependency 'rubocop'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+
29
+ spec.add_runtime_dependency 'fog', '>= 1.19'
30
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: UTF-8
2
+ require 'foghorn/version'
3
+ require 'fog'
4
+ require 'set'
5
+
6
+ # Foghorn
7
+ module Foghorn
8
+ def self.roles_for(object)
9
+ (object.tags['Roles'] || '').split(/\W+/)
10
+ end
11
+
12
+ # This is the class that interacts with fog
13
+ class Horn
14
+ def initialize(cxt)
15
+ if cxt.respond_to? :[]
16
+ @context = cxt
17
+ else
18
+ @context = ->(v) {cxt.fetch(v) }
19
+ end
20
+ @fog = Fog::Compute.new(@context['foghorn_config'])
21
+ @active_servers = Set.new
22
+ end
23
+
24
+ attr_reader :active_servers, :context
25
+
26
+ # Get list of servers, optionally filtered by a list of roles
27
+ def servers(*roles)
28
+ if @servers.nil?
29
+ @servers = @fog.servers
30
+ @servers_by_role = Hash.new { |h, k| h[k] = [] }
31
+ @servers.each do |server|
32
+ Foghorn.roles_for(server).each do |role|
33
+ @servers_by_role[role] << server
34
+ end
35
+ end
36
+ end
37
+ if roles.empty?
38
+ return @servers
39
+ else
40
+ roles = roles.flatten(1) if roles.length == 1
41
+ return roles
42
+ .map { |role| @servers_by_role[role.to_s] }
43
+ .inject([], :|)
44
+ end
45
+ end
46
+
47
+ # Safeguards against forgetting to tag a server
48
+ def safeguard
49
+ servers
50
+ .reject { |server| server.tags['Roles'] }
51
+ .each do |server|
52
+ fail "Server #{server.tags['Name']} has no tagged Roles! "\
53
+ 'Add a Roles tag to this server before deploying.'
54
+ end
55
+ end
56
+
57
+ # Creates snapshots of all active servers
58
+ def snapshots
59
+ active_servers.each do |server|
60
+ next unless server.tags['Snapshot']
61
+
62
+ server.volumes.each.with_index do |volume, index|
63
+ date = Time.now.strftime('%Y%m%d-%H%M')
64
+
65
+ name = "#{server.tags['Name']}-volume#{index}-#{date}"
66
+
67
+ snapshot = @fog.snapshots.new
68
+ snapshot.description = name
69
+ snapshot.volume_id = volume.id
70
+
71
+ #puts "Saving snapshot #{name}"
72
+ snapshot.save
73
+ snapshot.reload # to get id for tags
74
+
75
+ {
76
+ Name: name,
77
+ Time: Time.now.to_i,
78
+ Server: server.tags['Name'],
79
+ Revision: context['current_revision']
80
+ }.each do |key, value|
81
+ @fog.tags.create resource_id: snapshot.id, key: key, value: value
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Delete old snapshots - all but 2
88
+ def snapshots_cleanup
89
+ server_names = servers
90
+ .select { |server| server.tags['Snapshot']}
91
+ .map { |server| server.tags['Name'] }
92
+
93
+ snaps_by_server = Hash.new { |h, k| h[k] = [] }
94
+ @fog.snapshots
95
+ .select { |snapshot| server_names.include? snapshot.tags['Server'] }
96
+ .each do |snapshot|
97
+ snaps_by_server[snapshot.tags['Server']] << snapshot
98
+ end
99
+
100
+ snaps_by_server.each do |_server, snapshots|
101
+ snapshots.sort_by! do |snapshot|
102
+ (snapshot.tags['Time'] || 0).to_i * -1
103
+ end
104
+
105
+ snapshots.shift 2
106
+
107
+ snapshots.each do |snapshot|
108
+ begin
109
+ #puts "Deleting snapshot #{snapshot.tags['Name']}"
110
+ @fog.delete_snapshot snapshot.id
111
+ rescue => e
112
+ puts e.message
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+ require 'capistrano/version'
3
+
4
+ if defined?(Capistrano::VERSION) &&
5
+ Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
6
+ load File.expand_path File.join File.dirname(__FILE__),
7
+ 'tasks',
8
+ 'foghorn.rake'
9
+ else
10
+ load File.expand_path File.join File.dirname(__FILE__),
11
+ 'recipes.rb'
12
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+ require 'foghorn'
3
+
4
+ Capistrano::Configuration.instance.load do
5
+
6
+ def role_by_tag(r, *tags)
7
+ servers = foghorn.horn.servers(tags)
8
+ .select { |server| server.dns_name }
9
+ fail "Couldn't find any servers tagged with #{tags}" if servers.empty?
10
+ puts r
11
+ servers.each do |server|
12
+ puts " #{server.tags['Name']}"
13
+ foghorn.horn.active_servers << server
14
+ role r, server.dns_name
15
+ end
16
+ end
17
+
18
+ namespace :foghorn do
19
+ def horn
20
+ @foghorn ||= Foghorn::Horn.new(self)
21
+ end
22
+
23
+ desc 'Show roles for all servers'
24
+ task :blow do
25
+ horn.servers.each do |server|
26
+ puts(
27
+ ([server.tags['Name']] + Foghorn.roles_for(server))
28
+ .map { |col| col.ljust(16) }
29
+ .join)
30
+ end
31
+ end
32
+
33
+ desc 'Ensure all servers have tags'
34
+ task :safeguard do
35
+ horn.safeguard
36
+ end
37
+
38
+ desc 'Snapshot all deployable servers tagged "snapshot"'
39
+ task :snapshots do
40
+ horn.snapshots
41
+ end
42
+
43
+ desc 'Clean up old snapshots'
44
+ task :snapshots_cleanup do
45
+ horn.snapshots_cleanup
46
+ end
47
+ end
48
+ end
File without changes
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+ # Dolphin Deliver Version
3
+ module Foghorn
4
+ VERSION = '0.0.3'
5
+ end
data/spec.rb ADDED
@@ -0,0 +1,286 @@
1
+ # encoding: UTF-8
2
+ require 'foghorn'
3
+
4
+ # A fake Fog module to test Foghorn without a cloud
5
+ module FakeFog
6
+ # Compute
7
+ class Compute
8
+ def initialize(opts)
9
+ @servers = opts[:servers]
10
+ @snapshots = opts[:snapshots]
11
+ @tags = opts[:tags]
12
+ end
13
+
14
+ def delete_snapshot(id)
15
+ @snapshots.each do |snapshot|
16
+ if snapshot.id == id
17
+ snapshot.delete
18
+ return
19
+ end
20
+ end
21
+ end
22
+
23
+ attr_reader :servers, :snapshots, :tags
24
+ end
25
+
26
+ class Snapshot
27
+ attr_accessor :description, :volume_id, :id
28
+
29
+ def save
30
+ end
31
+
32
+ def reload
33
+ end
34
+ end
35
+ end
36
+
37
+ RSpec.describe Foghorn do
38
+ describe '#roles_for' do
39
+ it 'returns empty for nil Roles' do
40
+ obj = double('obj')
41
+ allow(obj).to receive(:tags) { {} }
42
+ expect(Foghorn.roles_for obj).to eq([])
43
+ end
44
+ it 'returns empty for blank Roles' do
45
+ obj = double('obj')
46
+ allow(obj).to receive(:tags) { { 'Roles' => '' } }
47
+ expect(Foghorn.roles_for obj).to eq([])
48
+ end
49
+ it 'returns a single Role' do
50
+ obj = double('obj')
51
+ allow(obj).to receive(:tags) { { 'Roles' => 'server' } }
52
+ expect(Foghorn.roles_for obj).to eq(%w(server))
53
+ end
54
+ it 'returns multiple Roles separated by spaces' do
55
+ obj = double('obj')
56
+ allow(obj).to receive(:tags) { { 'Roles' => 'server db cron' } }
57
+ expect(Foghorn.roles_for obj).to eq(%w(server db cron))
58
+ end
59
+ it 'returns multiple Roles separated by commas' do
60
+ obj = double('obj')
61
+ allow(obj).to receive(:tags) { { 'Roles' => 'server, db, cron' } }
62
+ expect(Foghorn.roles_for obj).to eq(%w(server db cron))
63
+ end
64
+ end
65
+
66
+ describe 'Horn' do
67
+ describe '#servers' do
68
+ it 'returns empty with no servers' do
69
+ stub_const('Fog', FakeFog)
70
+ horn = Foghorn::Horn.new(
71
+ 'foghorn_config' => { servers: [] })
72
+
73
+ expect(horn.servers).to eq([])
74
+ end
75
+
76
+ it 'lists servers' do
77
+ app1 = double('app1')
78
+ allow(app1).to receive(:tags) {
79
+ { 'Name' => 'app-001', 'Roles' => 'app db' }
80
+ }
81
+ app2 = double('app2')
82
+ allow(app2).to receive(:tags) {
83
+ { 'Name' => 'app-002', 'Roles' => 'app' }
84
+ }
85
+
86
+ stub_const('Fog', FakeFog)
87
+ horn = Foghorn::Horn.new(
88
+ 'foghorn_config' => { servers: [app1, app2] })
89
+
90
+ expect(horn.servers).to eq([app1, app2])
91
+ end
92
+
93
+ it 'lists servers by roles' do
94
+ app1 = double('app1')
95
+ allow(app1).to receive(:tags) {
96
+ { 'Name' => 'app-001', 'Roles' => 'app db' }
97
+ }
98
+ app2 = double('app2')
99
+ allow(app2).to receive(:tags) {
100
+ { 'Name' => 'app-002', 'Roles' => 'app' }
101
+ }
102
+
103
+ stub_const('Fog', FakeFog)
104
+ horn = Foghorn::Horn.new(
105
+ 'foghorn_config' => { servers: [app1, app2] })
106
+
107
+ expect(horn.servers('app')).to eq([app1, app2])
108
+ expect(horn.servers('db')).to eq([app1])
109
+ expect(horn.servers('cron')).to eq([])
110
+
111
+ expect(horn.servers('app', 'db', 'cron')).to eq([app1, app2])
112
+ expect(horn.servers('db', 'cron')).to eq([app1])
113
+ end
114
+ end
115
+
116
+ describe '#safeguard' do
117
+ it 'passes with no servers' do
118
+ stub_const('Fog', FakeFog)
119
+ horn = Foghorn::Horn.new(
120
+ 'foghorn_config' => { servers: [] })
121
+
122
+ horn.safeguard
123
+ end
124
+
125
+ it 'passes when servers are properly tagged' do
126
+ app1 = double('app1')
127
+ allow(app1).to receive(:tags) {
128
+ { 'Name' => 'app-001', 'Roles' => 'app db' }
129
+ }
130
+ app2 = double('app2')
131
+ allow(app2).to receive(:tags) {
132
+ { 'Name' => 'app-002', 'Roles' => 'app' }
133
+ }
134
+
135
+ stub_const('Fog', FakeFog)
136
+ horn = Foghorn::Horn.new(
137
+ 'foghorn_config' => { servers: [app1, app2] })
138
+
139
+ horn.safeguard
140
+ end
141
+
142
+ it 'fails when a server is not tagged' do
143
+ app1 = double('app1')
144
+ allow(app1).to receive(:tags) {
145
+ { 'Name' => 'app-001', 'Roles' => 'app db' }
146
+ }
147
+ app2 = double('app2')
148
+ allow(app2).to receive(:tags) {
149
+ { 'Name' => 'app-002' }
150
+ }
151
+
152
+ stub_const('Fog', FakeFog)
153
+ horn = Foghorn::Horn.new(
154
+ 'foghorn_config' => { servers: [app1, app2] })
155
+
156
+ expect { horn.safeguard }.to raise_error
157
+ end
158
+ end
159
+
160
+ describe '#snapshot' do
161
+ it 'makes snapshots' do
162
+ app1 = double('app1')
163
+ allow(app1).to receive(:tags) {
164
+ { 'Name' => 'app-001', 'Snapshot' => 'app' }
165
+ }
166
+ vol0 = double('volume')
167
+ allow(vol0).to receive(:id) { 'sda' }
168
+ allow(app1).to receive(:volumes) { [ vol0 ] }
169
+
170
+ snaps = double('snaps')
171
+ expect(snaps).to receive(:new){ FakeFog::Snapshot.new }.once
172
+
173
+ tags = double('tags')
174
+ expect(tags).to receive(:create).exactly(4).times
175
+
176
+ stub_const('Fog', FakeFog)
177
+ horn = Foghorn::Horn.new(
178
+ 'foghorn_config' => { servers: [app1], snapshots: snaps, tags: tags })
179
+ horn.active_servers << app1
180
+
181
+ horn.snapshots
182
+ end
183
+ end
184
+
185
+ describe '#snapshots_cleanup' do
186
+ it 'deletes old snapshots' do
187
+ app1 = double('app1')
188
+ allow(app1).to receive(:tags) {
189
+ { 'Name' => 'app-001', 'Snapshot' => 'app' }
190
+ }
191
+ snap1 = double('snap1')
192
+ allow(snap1).to receive(:id) { 'snap-1' }
193
+ expect(snap1).to receive(:delete).once
194
+ allow(snap1).to receive(:tags) {
195
+ { 'Server' => 'app-001', 'Time' => 413}
196
+ }
197
+ snap2 = double('snap2')
198
+ allow(snap2).to receive(:id) { 'snap-2' }
199
+ allow(snap2).to receive(:tags) {
200
+ { 'Server' => 'app-001', 'Time' => 1024}
201
+ }
202
+ snap3 = double('snap3')
203
+ allow(snap3).to receive(:id) { 'snap-3' }
204
+ allow(snap3).to receive(:tags) {
205
+ { 'Server' => 'app-001', 'Time' => 612}
206
+ }
207
+
208
+ stub_const('Fog', FakeFog)
209
+ horn = Foghorn::Horn.new(
210
+ 'foghorn_config' => {
211
+ servers: [app1],
212
+ snapshots: [snap1, snap2, snap3] })
213
+
214
+ horn.snapshots_cleanup
215
+ end
216
+
217
+ it 'preserves irrelevant snapshots' do
218
+ app1 = double('app1')
219
+ allow(app1).to receive(:tags) {
220
+ { 'Name' => 'app-001', 'Snapshot' => 'app' }
221
+ }
222
+ app2 = double('app2')
223
+ allow(app2).to receive(:tags) {
224
+ { 'Name' => 'app-002' }
225
+ }
226
+ snap1 = double('snap1')
227
+ allow(snap1).to receive(:id) { 'snap-1' }
228
+ allow(snap1).to receive(:tags) {
229
+ { 'Server' => 'app-002', 'Time' => 413}
230
+ }
231
+ snap2 = double('snap2')
232
+ allow(snap2).to receive(:id) { 'snap-2' }
233
+ allow(snap2).to receive(:tags) {
234
+ { 'Server' => 'app-002', 'Time' => 1024}
235
+ }
236
+ snap3 = double('snap3')
237
+ allow(snap3).to receive(:id) { 'snap-3' }
238
+ allow(snap3).to receive(:tags) {
239
+ { 'Server' => 'app-002', 'Time' => 612}
240
+ }
241
+
242
+ stub_const('Fog', FakeFog)
243
+ horn = Foghorn::Horn.new(
244
+ 'foghorn_config' => {
245
+ servers: [app1, app2],
246
+ snapshots: [snap1, snap2, snap3] })
247
+
248
+ horn.snapshots_cleanup
249
+ end
250
+
251
+ it 'keeps 2 snapshots from each server' do
252
+ app1 = double('app1')
253
+ allow(app1).to receive(:tags) {
254
+ { 'Name' => 'app-001', 'Snapshot' => 'app' }
255
+ }
256
+ api1 = double('app1')
257
+ allow(api1).to receive(:tags) {
258
+ { 'Name' => 'api-001', 'Snapshot' => 'api' }
259
+ }
260
+ snap1 = double('snap1')
261
+ allow(snap1).to receive(:id) { 'snap-1' }
262
+ allow(snap1).to receive(:tags) {
263
+ { 'Server' => 'app-001', 'Time' => 413}
264
+ }
265
+ snap2 = double('snap2')
266
+ allow(snap2).to receive(:id) { 'snap-2' }
267
+ allow(snap2).to receive(:tags) {
268
+ { 'Server' => 'api-001', 'Time' => 1024}
269
+ }
270
+ snap3 = double('snap3')
271
+ allow(snap3).to receive(:id) { 'snap-3' }
272
+ allow(snap3).to receive(:tags) {
273
+ { 'Server' => 'api-001', 'Time' => 612}
274
+ }
275
+
276
+ stub_const('Fog', FakeFog)
277
+ horn = Foghorn::Horn.new(
278
+ 'foghorn_config' => {
279
+ servers: [app1, api1],
280
+ snapshots: [snap1, snap2, snap3] })
281
+
282
+ horn.snapshots_cleanup
283
+ end
284
+ end
285
+ end
286
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foghorn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Chris Dollard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-03 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
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: rb-readline
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry_debug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fog
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '1.19'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '1.19'
125
+ description: Deploy to the cloud
126
+ email:
127
+ - chrisdollard@dolphinmicro.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rubocop.yml
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - foghorn.gemspec
139
+ - lib/foghorn.rb
140
+ - lib/foghorn/capistrano.rb
141
+ - lib/foghorn/recipes.rb
142
+ - lib/foghorn/tasks/foghorn.rake
143
+ - lib/foghorn/version.rb
144
+ - spec.rb
145
+ homepage: https://bitbucket.org/dolphinmicro/foghorn
146
+ licenses:
147
+ - MIT
148
+ metadata: {}
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 2.4.1
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Tag roles in EC2, not in your deploy scripts
169
+ test_files: []