guignol 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/commands/base'
29
+ require 'guignol/instance'
30
+ require 'term/ansicolor'
31
+
32
+ module Guignol::Commands
33
+ class List < Base
34
+ def initialize(*argv)
35
+ argv = ['.*'] if argv.empty?
36
+ super(*argv)
37
+ end
38
+
39
+ def run_on_server(config)
40
+ instance = Guignol::Instance.new(config)
41
+ puts "%s:\t%s" % [instance.name, colorize(instance.state)]
42
+ end
43
+
44
+ def self.short_usage
45
+ ["[regexp]", "List known instances (matching the regexp) and their status."]
46
+ end
47
+
48
+ private
49
+
50
+ def colorize(state)
51
+ case state
52
+ when 'running' then Term::ANSIColor.green(state)
53
+ when 'stopped' then Term::ANSIColor.yellow(state)
54
+ when 'nonexistent' then Term::ANSIColor.red(state)
55
+ else state
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,42 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/commands/base'
29
+ require 'guignol/instance'
30
+
31
+ module Guignol::Commands
32
+ class Start < Base
33
+ def run_on_server(config)
34
+ Guignol::Instance.new(config).start
35
+ end
36
+
37
+ def self.short_usage
38
+ ["<regexps>", "Start instances (unless they're running) and setup DNS"]
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/commands/base'
29
+ require 'guignol/instance'
30
+
31
+ module Guignol::Commands
32
+ class Stop < Base
33
+ def before_run
34
+ return true if @configs.empty?
35
+ names = @configs.map { |config| config[:name] }.join(", ")
36
+ confirm "Are you sure you want to stop servers #{names}"
37
+ end
38
+
39
+ def run_on_server(config)
40
+ Guignol::Instance.new(config).stop
41
+ end
42
+
43
+ def self.short_usage
44
+ ["<regexps>", "Stop instances (if they're running) and remove DNS"]
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,41 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/commands/base'
29
+ require 'uuidtools'
30
+
31
+ module Guignol::Commands
32
+ class UUID
33
+ def run
34
+ puts UUIDTools::UUID.random_create.to_s.upcase
35
+ end
36
+
37
+ def self.short_usage
38
+ ["", "Return a brand new UUID"]
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/commands/create'
29
+ require 'guignol/commands/kill'
30
+ require 'guignol/commands/start'
31
+ require 'guignol/commands/stop'
32
+ require 'guignol/commands/help'
33
+ require 'guignol/commands/list'
34
+ require 'guignol/commands/uuid'
35
+
36
+ module Guignol::Commands
37
+ Map = {
38
+ 'create' => Guignol::Commands::Create,
39
+ 'kill' => Guignol::Commands::Kill,
40
+ 'start' => Guignol::Commands::Start,
41
+ 'stop' => Guignol::Commands::Stop,
42
+ 'help' => Guignol::Commands::Help,
43
+ 'list' => Guignol::Commands::List,
44
+ 'uuid' => Guignol::Commands::UUID,
45
+ }
46
+ end
@@ -0,0 +1,270 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'yaml'
29
+ require 'fog'
30
+ require 'md5'
31
+ require 'active_support/core_ext/hash/slice'
32
+ require 'guignol'
33
+ require 'guignol/shared'
34
+ require 'guignol/volume'
35
+
36
+ module Guignol
37
+ class Instance
38
+ include Shared
39
+
40
+ def initialize(options)
41
+ @options = options.dup
42
+ require_options :name, :uuid
43
+
44
+ @options[:volumes] ||= []
45
+ connection_options = DefaultConnectionOptions.dup.merge @options.slice(:region)
46
+
47
+ @connection = Fog::Compute.new(connection_options)
48
+
49
+ @subject = @connection.servers.
50
+ select { |s| s.state != 'terminated' }.
51
+ find { |s| s.tags['UUID'] == uuid }
52
+ end
53
+
54
+
55
+ def exist?
56
+ !!@subject
57
+ end
58
+
59
+
60
+ def name
61
+ @options[:name]
62
+ end
63
+
64
+
65
+ def domain
66
+ @options[:domain]
67
+ end
68
+
69
+
70
+ def uuid
71
+ @options[:uuid]
72
+ end
73
+
74
+
75
+ def fqdn
76
+ name and domain and "#{name}.#{domain}"
77
+ end
78
+
79
+ def state
80
+ exist? and @subject.state or 'nonexistent'
81
+ end
82
+
83
+ def create
84
+ log "server already exists" and return self if exist?
85
+
86
+ options = DefaultServerOptions.dup.merge @options.slice(:image_id, :flavor_id, :key_name, :security_group_ids, :user_data)
87
+
88
+ # check for pre-existing volume(s). if any exist, add their AZ to the server's options
89
+ zones = @options[:volumes].map { |volume_options| Volume.new(volume_options).availability_zone }.compact.uniq
90
+ if zones.size > 1
91
+ raise "pre-existing volumes volumes are in different availability zones"
92
+ elsif zones.size == 1
93
+ log "using AZ '#{zones.first}' since volumes already exist"
94
+ options[:availability_zone] = zones.first
95
+ end
96
+
97
+ log "building server..."
98
+ @subject = @connection.servers.create(options)
99
+ setup
100
+ log "created as #{@subject.dns_name}"
101
+
102
+ return self
103
+ rescue Exception => e
104
+ log "error while creating (#{e.class.name})"
105
+ destroy
106
+ raise
107
+ end
108
+
109
+
110
+ def start
111
+ log "server doesn't exist (ignoring)" and return unless exist?
112
+ wait_for_state_transitions
113
+
114
+ if @subject.state != "stopped"
115
+ log "server #{@subject.state}."
116
+ else
117
+ log "starting server..."
118
+ @subject.start
119
+ setup
120
+ log "server started"
121
+ end
122
+ return self
123
+ end
124
+
125
+ # shared between create and start
126
+ def setup
127
+ update_tags
128
+ log "waiting for public dns to be set up..."
129
+ wait_for { @subject.dns_name }
130
+ update_dns
131
+ update_volumes
132
+ wait_for_state 'running'
133
+ return self
134
+ end
135
+
136
+ def stop
137
+ wait_for_state_transitions
138
+ if !exist?
139
+ log "server doesn't exist (ignoring)."
140
+ elsif @subject.state != "running"
141
+ log "server #{@subject.state}."
142
+ else
143
+ log "stopping server..."
144
+ remove_dns
145
+ @subject.stop
146
+ wait_for_state 'stopped', 'terminated'
147
+ end
148
+ return self
149
+ end
150
+
151
+
152
+ def destroy
153
+ if !exist?
154
+ log "server doesn't exist (ignoring)."
155
+ else
156
+ log "tearing server down..."
157
+ remove_dns
158
+ @subject.destroy
159
+ wait_for_state 'stopped', 'terminated'
160
+ # FIXME: remove tags here
161
+ @subject = nil
162
+ end
163
+ return self
164
+ end
165
+
166
+
167
+ def update_tags
168
+ log "updating server tags"
169
+ tags = { 'Name' => name, 'Domain' => domain, 'UUID' => uuid }
170
+ response = @connection.create_tags(@subject.id, tags)
171
+ raise "failed" unless response.status == 200
172
+ return self
173
+ end
174
+
175
+
176
+ def update_root_volume_tags
177
+ log "updating root volume tags"
178
+ tags = { 'Name' => "#{name}-root", 'UUID' => uuid }
179
+ response = @connection.create_tags(@subject.volumes.first.id, tags)
180
+ raise "failed" unless response.status == 200
181
+ return self
182
+ end
183
+
184
+
185
+ def update_volumes
186
+ update_root_volume_tags
187
+
188
+ @options[:volumes].each do |options|
189
+ options[:availability_zone] = @subject.availability_zone
190
+ Volume.new(options).attach(@subject.id)
191
+ end
192
+ end
193
+
194
+
195
+ def update_dns
196
+ return unless @options[:domain]
197
+ log "updating dns zone"
198
+
199
+ unless dns_zone
200
+ log "dns zone does not exist"
201
+ return self
202
+ end
203
+
204
+ if record = dns_record
205
+ if dns_record_matches?(record)
206
+ log "DNS record already exists"
207
+ return self
208
+ else
209
+ log "warning, while creating, DNS record exists but points to wrong server (fixing)"
210
+ record.destroy
211
+ end
212
+ end
213
+
214
+ dns_zone.records.create(:name => fqdn, :type => 'CNAME', :value => @subject.dns_name, :ttl => 5)
215
+ log "#{fqdn} -> #{@subject.dns_name}"
216
+ return self
217
+ end
218
+
219
+
220
+ def remove_dns
221
+ return unless @options[:domain]
222
+ log "removing dns record"
223
+
224
+ unless dns_zone
225
+ log "dns zone does not exist"
226
+ return self
227
+ end
228
+
229
+ if record = dns_record
230
+ unless dns_record_matches?(record)
231
+ log "warning, while removing, DNS record exist but does not point to the current server"
232
+ end
233
+ record.destroy
234
+ end
235
+
236
+ return self
237
+ end
238
+
239
+
240
+ private
241
+
242
+
243
+ def wait_for_state_transitions
244
+ return unless @subject
245
+ return unless %w(stopping pending).include? @subject.state
246
+ log "waiting for state transition from '#{@subject.state}' to complete"
247
+ wait_for { @subject.state != 'pending' }
248
+ wait_for { @subject.state != 'stopping' }
249
+ end
250
+
251
+
252
+ def dns_connection
253
+ @dns_connection ||= Fog::DNS.new(:provider => :aws)
254
+ end
255
+
256
+
257
+ def dns_zone
258
+ @dns_zone ||= dns_connection.zones.find { |zone| zone.domain == domain }
259
+ end
260
+
261
+ def dns_record
262
+ dns_zone.records.find { |record| record.name == fqdn }
263
+ end
264
+
265
+ def dns_record_matches?(record)
266
+ !!record and record.value.any? { |dns_name| dns_name == @subject.dns_name }
267
+ end
268
+
269
+ end
270
+ end
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'guignol/tty_spinner'
29
+
30
+ module Guignol
31
+ module Shared
32
+
33
+
34
+ def log message
35
+ stamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
36
+ $stderr.write("[#{stamp}] #{name}: #{message}\n")
37
+ $stderr.flush
38
+ true
39
+ end
40
+
41
+
42
+ def subject_name
43
+ self.class.name.gsub(/.*:/,'').downcase
44
+ end
45
+
46
+
47
+ def wait_for_state(*states)
48
+ @subject or raise "#{subject_name} doesn't exist"
49
+ original_state = @subject.state
50
+ unless states.include?(original_state)
51
+ log "waiting for #{subject_name} to become #{states.join(' or ')}..."
52
+ wait_for do
53
+ if @subject.state != original_state
54
+ log "#{subject_name} now #{@subject.state}"
55
+ original_state = @subject.state
56
+ end
57
+ states.include?(@subject.state)
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ def wait_for(&block)
64
+ return unless @subject
65
+ @subject.wait_for { TtySpinner.spin! ; block.call }
66
+ end
67
+
68
+ def confirm(message)
69
+ puts "#{message} [y/n]"
70
+ $stdin.gets =~ /^y$/i
71
+ end
72
+
73
+ def require_options(*required_options)
74
+ required_options.each do |required_option|
75
+ next if @options.include?(required_option)
76
+ raise "option '#{required_option}' is mandatory for each #{subject_name}"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'term/ansicolor'
29
+ require 'guignol/commands'
30
+
31
+ module Guignol
32
+ class Shell
33
+ include Singleton
34
+
35
+ def execute(command_name, *argv)
36
+ command_name ||= 'help'
37
+ unless command = Commands::Map[command_name]
38
+ Commands::Help.new.run
39
+ die "no such command '#{command_name}'."
40
+ end
41
+ command.new(*argv).run
42
+ exit 0
43
+ end
44
+
45
+ def die(message)
46
+ puts Term::ANSIColor.red("fatal: #{message}")
47
+ exit 1
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2012, HouseTrip SA.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ #
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+ #
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the authors.
27
+
28
+ require 'singleton'
29
+
30
+ module Guignol
31
+ class TtySpinner
32
+ include Singleton
33
+
34
+ Chars = ['/','|','\\', '-']
35
+
36
+ def initialize
37
+ @state = 0
38
+ end
39
+
40
+ def spin!
41
+ $stderr.write(Chars[@state % Chars.size] + "\r")
42
+ $stderr.flush
43
+ @state += 1
44
+ Thread.pass
45
+ end
46
+
47
+ def self.spin!
48
+ instance.spin!
49
+ end
50
+
51
+ end
52
+ end