guignol 0.1

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,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