guignol 0.1.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rspec +2 -1
- data/Gemfile.lock +29 -18
- data/LICENCE +26 -0
- data/README.md +67 -38
- data/Rakefile +0 -26
- data/bin/guignol +3 -33
- data/guignol.gemspec +4 -30
- data/lib/core_ext/array/collect_key.rb +6 -0
- data/lib/core_ext/hash/map_to_hash.rb +31 -0
- data/lib/guignol.rb +15 -35
- data/lib/guignol/commands/base.rb +53 -66
- data/lib/guignol/commands/clone.rb +49 -0
- data/lib/guignol/commands/create.rb +14 -33
- data/lib/guignol/commands/execute.rb +69 -0
- data/lib/guignol/commands/fix_dns.rb +12 -33
- data/lib/guignol/commands/kill.rb +18 -36
- data/lib/guignol/commands/list.rb +19 -45
- data/lib/guignol/commands/start.rb +14 -33
- data/lib/guignol/commands/stop.rb +18 -37
- data/lib/guignol/commands/uuid.rb +19 -32
- data/lib/guignol/configuration.rb +43 -0
- data/lib/guignol/connection.rb +33 -0
- data/lib/guignol/env.rb +19 -0
- data/lib/guignol/logger.rb +29 -0
- data/lib/guignol/models/base.rb +125 -0
- data/lib/guignol/models/instance.rb +244 -0
- data/lib/guignol/models/volume.rb +91 -0
- data/lib/guignol/shell.rb +27 -42
- data/lib/guignol/tty_spinner.rb +6 -29
- data/lib/guignol/version.rb +1 -27
- data/spec/guignol/configuration_spec.rb +72 -0
- data/spec/guignol/instance_spec.rb +48 -8
- data/spec/guignol/volume_spec.rb +17 -0
- data/spec/spec_helper.rb +12 -0
- data/tmp/.keepme +0 -0
- metadata +79 -52
- data/lib/guignol/array/collect_key.rb +0 -32
- data/lib/guignol/commands.rb +0 -48
- data/lib/guignol/commands/help.rb +0 -77
- data/lib/guignol/instance.rb +0 -270
- data/lib/guignol/shared.rb +0 -80
- data/lib/guignol/volume.rb +0 -124
@@ -1,60 +1,34 @@
|
|
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
1
|
require 'guignol/commands/base'
|
29
|
-
|
30
|
-
|
2
|
+
|
3
|
+
Guignol::Shell.class_eval do
|
4
|
+
desc 'list [PATTERNS]', 'List the status of all known instances'
|
5
|
+
def list(*patterns)
|
6
|
+
patterns.push('.*') if patterns.empty?
|
7
|
+
Guignol::Commands::List.new(patterns).run
|
8
|
+
end
|
9
|
+
end
|
31
10
|
|
32
11
|
module Guignol::Commands
|
33
12
|
class List < Base
|
34
|
-
|
35
|
-
argv = ['.*'] if argv.empty?
|
36
|
-
super(*argv)
|
37
|
-
end
|
13
|
+
private
|
38
14
|
|
39
|
-
def run_on_server(
|
40
|
-
|
41
|
-
|
15
|
+
def run_on_server(instance, options = {})
|
16
|
+
synchronize do
|
17
|
+
shell.say instance.name.ljust(@max_width + 1)
|
18
|
+
shell.say instance.state, colorize(instance.state)
|
19
|
+
end
|
42
20
|
end
|
43
21
|
|
44
|
-
def
|
45
|
-
|
22
|
+
def before_run(configs)
|
23
|
+
@max_width = configs.keys.map(&:size).max
|
46
24
|
end
|
47
25
|
|
48
|
-
private
|
49
|
-
|
50
26
|
def colorize(state)
|
51
27
|
case state
|
52
|
-
when 'running'
|
53
|
-
when
|
54
|
-
when 'nonexistent'
|
55
|
-
else state
|
28
|
+
when 'running' then :green
|
29
|
+
when /starting|stopping/ then :yellow
|
30
|
+
when 'nonexistent' then :red
|
56
31
|
end
|
57
32
|
end
|
58
33
|
end
|
59
34
|
end
|
60
|
-
|
@@ -1,41 +1,22 @@
|
|
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
1
|
|
28
2
|
require 'guignol/commands/base'
|
29
|
-
require 'guignol/instance'
|
3
|
+
require 'guignol/models/instance'
|
30
4
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
5
|
+
Guignol::Shell.class_eval do
|
6
|
+
desc 'start PATTERNS', 'Start all instances matching PATTERNS, attach their volumes, and setup DNS records'
|
7
|
+
def start(*patterns)
|
8
|
+
if patterns.empty?
|
9
|
+
raise Thor::Error.new('You must specify at least one PATTERN.')
|
35
10
|
end
|
11
|
+
Guignol::Commands::Start.new(patterns).run
|
12
|
+
end
|
13
|
+
end
|
36
14
|
|
37
|
-
|
38
|
-
|
15
|
+
|
16
|
+
module Guignol::Commands
|
17
|
+
class Start < Base
|
18
|
+
def run_on_server(instance, options = {})
|
19
|
+
instance.start
|
39
20
|
end
|
40
21
|
end
|
41
22
|
end
|
@@ -1,47 +1,28 @@
|
|
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
1
|
|
28
2
|
require 'guignol/commands/base'
|
29
|
-
require 'guignol/instance'
|
3
|
+
require 'guignol/models/instance'
|
30
4
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
confirm "Are you sure you want to stop servers #{names}"
|
5
|
+
Guignol::Shell.class_eval do
|
6
|
+
desc 'stop PATTERNS', 'Stop all instances matching PATTERNS, and remove DNS records'
|
7
|
+
def stop(*patterns)
|
8
|
+
if patterns.empty?
|
9
|
+
raise Thor::Error.new('You must specify at least one PATTERN.')
|
37
10
|
end
|
11
|
+
Guignol::Commands::Stop.new(patterns).run
|
12
|
+
end
|
13
|
+
end
|
38
14
|
|
39
|
-
|
40
|
-
|
15
|
+
|
16
|
+
module Guignol::Commands
|
17
|
+
class Stop < Base
|
18
|
+
def before_run(configs)
|
19
|
+
return true if configs.empty?
|
20
|
+
names = configs.keys.join(", ")
|
21
|
+
shell.yes? "Are you sure you want to stop servers #{names}? [y/N]", :cyan
|
41
22
|
end
|
42
23
|
|
43
|
-
def
|
44
|
-
|
24
|
+
def run_on_server(instance, options = {})
|
25
|
+
instance.stop
|
45
26
|
end
|
46
27
|
end
|
47
28
|
end
|
@@ -1,41 +1,28 @@
|
|
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
1
|
|
28
2
|
require 'guignol/commands/base'
|
29
3
|
require 'uuidtools'
|
30
4
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
5
|
+
Guignol::Shell.class_eval do
|
6
|
+
desc 'uuid [COUNT]', 'Print random UUIDs'
|
7
|
+
method_option :count,
|
8
|
+
:aliases => %w(-c),
|
9
|
+
:type => :numeric, :default => 1,
|
10
|
+
:desc => 'Number of UUIDs to print'
|
11
|
+
def uuid
|
12
|
+
unless options[:count].kind_of?(Fixnum) && options[:count] > 0
|
13
|
+
raise Thor::Error.new('Count should be a positive integer')
|
35
14
|
end
|
15
|
+
Guignol::Commands::UUID.new.run(options[:count])
|
16
|
+
end
|
17
|
+
end
|
36
18
|
|
37
|
-
|
38
|
-
|
19
|
+
|
20
|
+
module Guignol::Commands
|
21
|
+
class UUID
|
22
|
+
def run(count = 1)
|
23
|
+
count.times do
|
24
|
+
puts UUIDTools::UUID.random_create.to_s.upcase
|
25
|
+
end
|
39
26
|
end
|
40
27
|
end
|
41
28
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext/enumerable'
|
4
|
+
require 'guignol'
|
5
|
+
|
6
|
+
module Guignol::Configuration
|
7
|
+
|
8
|
+
def configuration
|
9
|
+
@configuration ||= load_config_file
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def config_file_path
|
15
|
+
@config_file_path ||= [
|
16
|
+
Pathname.new(ENV['GUIGNOL_YML'] || '/var/nonexistent'),
|
17
|
+
Pathname.new('guignol.yml'),
|
18
|
+
Pathname.new('config/guignol.yml'),
|
19
|
+
Pathname.new(ENV['HOME']).join('.guignol.yml')
|
20
|
+
].find(&:exist?)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Load the config hash for the file, converting old (v0.2.0) Yaml config files.
|
24
|
+
def load_config_file
|
25
|
+
return {} if config_file_path.nil?
|
26
|
+
data = YAML.load(config_file_path.read)
|
27
|
+
return data unless data.kind_of?(Array)
|
28
|
+
|
29
|
+
# Convert the toplevel array to a hash. Same for arrays of volumes.
|
30
|
+
Guignol.logger.warn "Configuration file '#{config_file_path}' uses the old array format. Trying to load it."
|
31
|
+
raise "Instance config lacks :name" unless data.collect_key(:name).all?
|
32
|
+
result = data.index_by { |item| item.delete(:name) }
|
33
|
+
result.each_pair do |name, config|
|
34
|
+
next unless config[:volumes]
|
35
|
+
raise "Volume config lacks :name" unless config[:volumes].collect_key(:name).all?
|
36
|
+
config[:volumes] = config[:volumes].index_by { |item| item.delete(:name) }
|
37
|
+
end
|
38
|
+
|
39
|
+
return result
|
40
|
+
end
|
41
|
+
|
42
|
+
Guignol.extend(self)
|
43
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require 'fog'
|
3
|
+
require 'active_support/core_ext/hash/slice'
|
4
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
5
|
+
require 'guignol/configuration'
|
6
|
+
|
7
|
+
module Guignol
|
8
|
+
# Pool Fog connections to minimize latency
|
9
|
+
module Connection
|
10
|
+
def self.get(options)
|
11
|
+
@connections ||= {}
|
12
|
+
@connections[options] ||= Fog::Compute.new(options)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
|
19
|
+
# Find and return credentials
|
20
|
+
def credentials
|
21
|
+
if ENV['AWS_SECRET_KEY_ID'] && ENV['AWS_SECRET_ACCESS_KEY']
|
22
|
+
{
|
23
|
+
:aws_access_key_id => ENV['AWS_SECRET_KEY_ID'],
|
24
|
+
:aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
|
25
|
+
}
|
26
|
+
else
|
27
|
+
Guignol.configuration.slice(:aws_access_key_id, :aws_secret_access_key)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/lib/guignol/env.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Guignol::Logger
|
4
|
+
def logger
|
5
|
+
@logger ||= ::Logger.new(logger_file).tap do |logger|
|
6
|
+
logger.progname = 'guignol'
|
7
|
+
logger.formatter = Formatter.new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
class Formatter < ::Logger::Formatter
|
15
|
+
Format = "[%s] %s: %s\n"
|
16
|
+
|
17
|
+
def call(severity, time, progname, msg)
|
18
|
+
Format % [time.strftime('%F %T'), severity, msg2str(msg)]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def logger_file
|
24
|
+
return File.open(ENV['GUIGNOL_LOG'] ,'a') if ENV['GUIGNOL_LOG']
|
25
|
+
$stdout.tty? ? $stdout : File.open('/dev/null','w')
|
26
|
+
end
|
27
|
+
|
28
|
+
Guignol.extend(self)
|
29
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
|
2
|
+
require 'guignol/connection'
|
3
|
+
require 'guignol/tty_spinner'
|
4
|
+
|
5
|
+
module Guignol::Models
|
6
|
+
class Base
|
7
|
+
# The wrapped instance, volume, etc we're manipulating
|
8
|
+
attr :subject
|
9
|
+
attr :options
|
10
|
+
attr :connection
|
11
|
+
attr :name
|
12
|
+
|
13
|
+
def initialize(name, options)
|
14
|
+
@name = name
|
15
|
+
@options = default_options.merge(options)
|
16
|
+
require_name!
|
17
|
+
require_options! :uuid
|
18
|
+
connection_options = Guignol::DefaultConnectionOptions.merge @options.slice(:region)
|
19
|
+
|
20
|
+
@connection = Guignol::Connection.get(connection_options)
|
21
|
+
@subject = find_subject
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def exist?
|
26
|
+
!!@subject
|
27
|
+
end
|
28
|
+
alias_method :exists?, :exist?
|
29
|
+
|
30
|
+
|
31
|
+
def uuid
|
32
|
+
@options[:uuid]
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def state
|
37
|
+
@subject and @subject.state or 'nonexistent'
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
|
44
|
+
def set_subject(subject)
|
45
|
+
@subject = subject
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def reload
|
50
|
+
@subject = find_subject
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def find_subject
|
55
|
+
raise 'Define me in a subclass'
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
Interval = 200e-3
|
60
|
+
Timeout = 300
|
61
|
+
|
62
|
+
|
63
|
+
def default_options
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
|
67
|
+
def log(message, options={})
|
68
|
+
Guignol.logger.info("#{name}: #{message}")
|
69
|
+
if e = options[:error]
|
70
|
+
Guignol.logger.info e.class.name
|
71
|
+
Guignol.logger.info e.message
|
72
|
+
e.backtrace.each { |line| Guignol.logger.debug line }
|
73
|
+
end
|
74
|
+
Thread.pass
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def subject_name
|
80
|
+
self.class.name.gsub(/.*:/,'').downcase
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# wait until the subject is in one of +states+
|
85
|
+
def wait_for_state(*states)
|
86
|
+
exist? or raise "#{subject_name} doesn't exist"
|
87
|
+
original_state = state
|
88
|
+
return if states.include?(original_state)
|
89
|
+
log "waiting for #{subject_name} to become #{states.join(' or ')}..."
|
90
|
+
Fog.wait_for(Timeout,Interval) do
|
91
|
+
Guignol::TtySpinner.spin!
|
92
|
+
reload
|
93
|
+
if state != original_state
|
94
|
+
log "#{subject_name} now #{state}"
|
95
|
+
original_state = state
|
96
|
+
end
|
97
|
+
states.include?(state)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def wait_for(&block)
|
103
|
+
return unless @subject
|
104
|
+
@subject.wait_for(Timeout,Interval) { Guignol::TtySpinner.spin! ; block.call }
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def confirm(message)
|
109
|
+
puts "#{message} [y/n]"
|
110
|
+
$stdin.gets =~ /^y$/i
|
111
|
+
end
|
112
|
+
|
113
|
+
def require_name!
|
114
|
+
return unless @name.nil? || @name =~ /^\s*$/
|
115
|
+
raise "Name cannot be empty or blank"
|
116
|
+
end
|
117
|
+
|
118
|
+
def require_options!(*required_options)
|
119
|
+
required_options.each do |required_option|
|
120
|
+
next if @options.include?(required_option)
|
121
|
+
raise "option '#{required_option}' is mandatory for each #{subject_name}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|