guignol 0.1.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|