vmreverter 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +20 -0
- data/Gemfile +5 -0
- data/LICENSE +12 -0
- data/README.md +121 -0
- data/Rakefile +11 -0
- data/bin/vmreverter +9 -0
- data/components.LICENSE.puppet_acceptance +17 -0
- data/lib/vmreverter/cli.rb +50 -0
- data/lib/vmreverter/config_tester.rb +63 -0
- data/lib/vmreverter/hypervisor/aws.rb +121 -0
- data/lib/vmreverter/hypervisor/vsphere.rb +96 -0
- data/lib/vmreverter/hypervisor/vsphere_helper.rb +187 -0
- data/lib/vmreverter/hypervisor.rb +34 -0
- data/lib/vmreverter/logger.rb +170 -0
- data/lib/vmreverter/options.rb +104 -0
- data/lib/vmreverter/shared/error_handler.rb +20 -0
- data/lib/vmreverter/shared.rb +13 -0
- data/lib/vmreverter/version.rb +3 -0
- data/lib/vmreverter/vmmanager.rb +60 -0
- data/lib/vmreverter.rb +29 -0
- data/vmreverter.gemspec +38 -0
- metadata +179 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
# Apache Licensed - (github/puppetlabs) ripped from puppet_acceptance. ** See Legal notes
|
2
|
+
# Changes include namespace swaps, and refactoring
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'Vmreverter/logger'
|
6
|
+
rescue LoadError
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'logger.rb'))
|
8
|
+
end
|
9
|
+
|
10
|
+
class VsphereHelper
|
11
|
+
|
12
|
+
#Class methods
|
13
|
+
def self.load_config authfile
|
14
|
+
vsphere_credentials = {}
|
15
|
+
|
16
|
+
if File.exists?(authfile)
|
17
|
+
vInfo = YAML.load_file(authfile)
|
18
|
+
elsif File.exists?( File.join(ENV['HOME'], '.fog') )
|
19
|
+
vInfo = YAML.load_file( File.join(ENV['HOME'], '.fog') )
|
20
|
+
else
|
21
|
+
report_and_raise(@logger, RuntimeError.new("Couldn't authentication for vSphere in auth file !"), "VSphereHelper::load_config")
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
vsphere_credentials[:server] = vInfo[:default][:vsphere_server]
|
26
|
+
vsphere_credentials[:user] = vInfo[:default][:vsphere_username]
|
27
|
+
vsphere_credentials[:pass] = vInfo[:default][:vsphere_password]
|
28
|
+
rescue
|
29
|
+
report_and_raise(@logger, RuntimeError.new("Couldn't load authentication for vSphere in auth file !"), "VSphereHelper::load_config")
|
30
|
+
end
|
31
|
+
|
32
|
+
return vsphere_credentials
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize vInfo = {}
|
36
|
+
@logger = vInfo[:logger] || Vmreverter::Logger.new
|
37
|
+
begin
|
38
|
+
require 'rbvmomi'
|
39
|
+
rescue LoadError
|
40
|
+
raise "Unable to load RbVmomi, please ensure its installed"
|
41
|
+
end
|
42
|
+
|
43
|
+
# If you don’t have trusted SSL certificates installed on the host you’re connecting to,
|
44
|
+
#you’ll get an +OpenSSL::SSL::SSLError+ “certificate verify failed”.
|
45
|
+
#You can work around this by using the :insecure option to +RbVmomi::VIM.connect+.
|
46
|
+
|
47
|
+
@connection = RbVmomi::VIM.connect :host => vInfo[:server],
|
48
|
+
:user => vInfo[:user],
|
49
|
+
:password => vInfo[:pass],
|
50
|
+
:insecure => true
|
51
|
+
end
|
52
|
+
#Instance Methods
|
53
|
+
def find_snapshot vm, snapname
|
54
|
+
search_child_snaps vm.snapshot.rootSnapshotList, snapname
|
55
|
+
end
|
56
|
+
|
57
|
+
def search_child_snaps tree, snapname
|
58
|
+
snapshot = nil
|
59
|
+
tree.each do |child|
|
60
|
+
if child.name == snapname
|
61
|
+
snapshot ||= child.snapshot
|
62
|
+
else
|
63
|
+
snapshot ||= search_child_snaps child.childSnapshotList, snapname
|
64
|
+
end
|
65
|
+
end
|
66
|
+
snapshot
|
67
|
+
end
|
68
|
+
|
69
|
+
def find_customization name
|
70
|
+
csm = @connection.serviceContent.customizationSpecManager
|
71
|
+
|
72
|
+
begin
|
73
|
+
customizationSpec = csm.GetCustomizationSpec({:name => name}).spec
|
74
|
+
rescue
|
75
|
+
customizationSpec = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
return customizationSpec
|
79
|
+
end
|
80
|
+
|
81
|
+
# an easier wrapper around the horrid PropertyCollector interface,
|
82
|
+
# necessary for searching VMs in all Datacenters that may be nested
|
83
|
+
# within folders of arbitrary depth
|
84
|
+
# returns a hash array of <name> => <VirtualMachine ManagedObjects>
|
85
|
+
def find_vms names, connection = @connection
|
86
|
+
names = names.is_a?(Array) ? names : [ names ]
|
87
|
+
containerView = get_base_vm_container_from connection
|
88
|
+
propertyCollector = connection.propertyCollector
|
89
|
+
|
90
|
+
objectSet = [{
|
91
|
+
:obj => containerView,
|
92
|
+
:skip => true,
|
93
|
+
:selectSet => [ RbVmomi::VIM::TraversalSpec.new({
|
94
|
+
:name => 'gettingTheVMs',
|
95
|
+
:path => 'view',
|
96
|
+
:skip => false,
|
97
|
+
:type => 'ContainerView'
|
98
|
+
}) ]
|
99
|
+
}]
|
100
|
+
|
101
|
+
propSet = [{
|
102
|
+
:pathSet => [ 'name' ],
|
103
|
+
:type => 'VirtualMachine'
|
104
|
+
}]
|
105
|
+
|
106
|
+
results = propertyCollector.RetrievePropertiesEx({
|
107
|
+
:specSet => [{
|
108
|
+
:objectSet => objectSet,
|
109
|
+
:propSet => propSet
|
110
|
+
}],
|
111
|
+
:options => { :maxObjects => nil }
|
112
|
+
})
|
113
|
+
|
114
|
+
vms = {}
|
115
|
+
results.objects.each do |result|
|
116
|
+
name = result.propSet.first.val
|
117
|
+
next unless names.include? name
|
118
|
+
vms[name] = result.obj
|
119
|
+
end
|
120
|
+
|
121
|
+
while results.token do
|
122
|
+
results = propertyCollector.ContinueRetrievePropertiesEx({:token => results.token})
|
123
|
+
results.objects.each do |result|
|
124
|
+
name = result.propSet.first.val
|
125
|
+
next unless names.include? name
|
126
|
+
vms[name] = result.obj
|
127
|
+
end
|
128
|
+
end
|
129
|
+
vms
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_datastore datastorename
|
133
|
+
datacenter = @connection.serviceInstance.find_datacenter
|
134
|
+
datacenter.find_datastore(datastorename)
|
135
|
+
end
|
136
|
+
|
137
|
+
def find_folder foldername
|
138
|
+
datacenter = @connection.serviceInstance.find_datacenter
|
139
|
+
base = datacenter.vmFolder
|
140
|
+
folders = foldername.split('/')
|
141
|
+
folders.each do |folder|
|
142
|
+
case base
|
143
|
+
when RbVmomi::VIM::Folder
|
144
|
+
base = base.childEntity.find { |f| f.name == folder }
|
145
|
+
else
|
146
|
+
abort "Unexpected object type encountered (#{base.class}) while finding folder"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
base
|
151
|
+
end
|
152
|
+
|
153
|
+
def find_pool poolname
|
154
|
+
datacenter = @connection.serviceInstance.find_datacenter
|
155
|
+
base = datacenter.hostFolder
|
156
|
+
pools = poolname.split('/')
|
157
|
+
pools.each do |pool|
|
158
|
+
case base
|
159
|
+
when RbVmomi::VIM::Folder
|
160
|
+
base = base.childEntity.find { |f| f.name == pool }
|
161
|
+
when RbVmomi::VIM::ClusterComputeResource
|
162
|
+
base = base.resourcePool.resourcePool.find { |f| f.name == pool }
|
163
|
+
when RbVmomi::VIM::ResourcePool
|
164
|
+
base = base.resourcePool.find { |f| f.name == pool }
|
165
|
+
else
|
166
|
+
abort "Unexpected object type encountered (#{base.class}) while finding resource pool"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
base = base.resourcePool unless base.is_a?(RbVmomi::VIM::ResourcePool) and base.respond_to?(:resourcePool)
|
171
|
+
base
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_base_vm_container_from connection
|
175
|
+
viewManager = connection.serviceContent.viewManager
|
176
|
+
viewManager.CreateContainerView({
|
177
|
+
:container => connection.serviceContent.rootFolder,
|
178
|
+
:recursive => true,
|
179
|
+
:type => [ 'VirtualMachine' ]
|
180
|
+
})
|
181
|
+
end
|
182
|
+
|
183
|
+
def close_connection
|
184
|
+
@connection.close
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Apache Licensed - (github/puppetlabs) ripped from puppet_acceptance. ** See Legal notes
|
2
|
+
# Changes include namespace swaps, method removal, method additions, and complete code refactoring
|
3
|
+
|
4
|
+
module Vmreverter
|
5
|
+
|
6
|
+
# Factory Pattern - Class to generate the correct hypervisor object, given type
|
7
|
+
class Hypervisor
|
8
|
+
|
9
|
+
def configure(hosts)
|
10
|
+
@logger.debug "No post-provisioning configuration necessary for #{self.class.name} boxes"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.register type, hosts_to_provision, options, config
|
14
|
+
@logger = options[:logger]
|
15
|
+
@logger.notify("Hypervisor found some #{type} boxes to hook")
|
16
|
+
case type.downcase
|
17
|
+
when /vsphere/
|
18
|
+
return Vmreverter::Vsphere.new hosts_to_provision, options, config
|
19
|
+
when /aws/
|
20
|
+
return Vmreverter::AWS.new hosts_to_provision, options, config
|
21
|
+
else
|
22
|
+
report_and_raise(@logger, RuntimeError.new("Missing Class for hypervisor invocation: (#{type})"), "Hypervisor::register")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
%w( vsphere_helper aws vsphere ).each do |lib|
|
29
|
+
begin
|
30
|
+
require "hypervisor/#{lib}"
|
31
|
+
rescue LoadError
|
32
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "hypervisor", lib))
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# Apache Licensed - (github/puppetlabs) ripped from puppet_acceptance. ** See Legal notes
|
2
|
+
# Changes include namespace swaps, method removal, method additions, and complete code refactoring
|
3
|
+
|
4
|
+
module Vmreverter
|
5
|
+
class Logger
|
6
|
+
NORMAL = "\e[00;00m"
|
7
|
+
BRIGHT_NORMAL = "\e[00;01m"
|
8
|
+
BLACK = "\e[00;30m"
|
9
|
+
RED = "\e[00;31m"
|
10
|
+
GREEN = "\e[00;32m"
|
11
|
+
YELLOW = "\e[00;33m"
|
12
|
+
BLUE = "\e[00;34m"
|
13
|
+
MAGENTA = "\e[00;35m"
|
14
|
+
CYAN = "\e[00;36m"
|
15
|
+
WHITE = "\e[00;37m"
|
16
|
+
GREY = "\e[01;30m"
|
17
|
+
BRIGHT_RED = "\e[01;31m"
|
18
|
+
BRIGHT_GREEN = "\e[01;32m"
|
19
|
+
BRIGHT_YELLOW = "\e[01;33m"
|
20
|
+
BRIGHT_BLUE = "\e[01;34m"
|
21
|
+
BRIGHT_MAGENTA = "\e[01;35m"
|
22
|
+
BRIGHT_CYAN = "\e[01;36m"
|
23
|
+
BRIGHT_WHITE = "\e[01;37m"
|
24
|
+
|
25
|
+
LOG_LEVELS = {
|
26
|
+
:debug => 1,
|
27
|
+
:warn => 2,
|
28
|
+
:normal => 3,
|
29
|
+
:info => 4
|
30
|
+
}
|
31
|
+
|
32
|
+
attr_accessor :color, :log_level, :destinations
|
33
|
+
|
34
|
+
def initialize(*args)
|
35
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
36
|
+
@color = options[:color]
|
37
|
+
@log_level = options[:debug] ? :debug : :normal
|
38
|
+
@destinations = []
|
39
|
+
|
40
|
+
dests = args
|
41
|
+
dests << STDOUT unless options[:quiet]
|
42
|
+
dests.uniq!
|
43
|
+
dests.each {|dest| add_destination(dest)}
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_destination(dest)
|
47
|
+
case dest
|
48
|
+
when IO
|
49
|
+
@destinations << dest
|
50
|
+
when String
|
51
|
+
@destinations << File.open(dest, 'w')
|
52
|
+
else
|
53
|
+
raise "Unsuitable log destination #{dest.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def remove_destination(dest)
|
58
|
+
case dest
|
59
|
+
when IO
|
60
|
+
@destinations.delete(dest)
|
61
|
+
when String
|
62
|
+
@destinations.delete_if {|d| d.respond_to?(:path) and d.path == dest}
|
63
|
+
else
|
64
|
+
raise "Unsuitable log destination #{dest.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_debug?
|
69
|
+
LOG_LEVELS[@log_level] <= LOG_LEVELS[:debug]
|
70
|
+
end
|
71
|
+
|
72
|
+
def is_warn?
|
73
|
+
LOG_LEVELS[@log_level] <= LOG_LEVELS[:warn]
|
74
|
+
end
|
75
|
+
|
76
|
+
def host_output *args
|
77
|
+
return unless is_debug?
|
78
|
+
strings = strip_colors_from args
|
79
|
+
string = strings.join
|
80
|
+
optionally_color GREY, string, false
|
81
|
+
end
|
82
|
+
|
83
|
+
def debug *args
|
84
|
+
return unless is_debug?
|
85
|
+
optionally_color WHITE, args
|
86
|
+
end
|
87
|
+
|
88
|
+
def warn *args
|
89
|
+
return unless is_warn?
|
90
|
+
strings = args.map {|msg| "Warning: #{msg}" }
|
91
|
+
optionally_color YELLOW, strings
|
92
|
+
end
|
93
|
+
|
94
|
+
def success *args
|
95
|
+
optionally_color GREEN, args
|
96
|
+
end
|
97
|
+
|
98
|
+
def notify *args
|
99
|
+
optionally_color BRIGHT_WHITE, args
|
100
|
+
end
|
101
|
+
|
102
|
+
def error *args
|
103
|
+
optionally_color BRIGHT_RED, args
|
104
|
+
end
|
105
|
+
|
106
|
+
def strip_colors_from lines
|
107
|
+
Array(lines).map do |line|
|
108
|
+
line.gsub /\e\[(\d+;)?\d+m/, ''
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def optionally_color color_code, msg, add_newline = true
|
113
|
+
print_statement = add_newline ? :puts : :print
|
114
|
+
@destinations.each do |to|
|
115
|
+
to.print color_code if @color
|
116
|
+
to.send print_statement, msg
|
117
|
+
to.print NORMAL if @color
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# utility method to get the current call stack and format it
|
122
|
+
# to a human-readable string (which some IDEs/editors
|
123
|
+
# will recognize as links to the line numbers in the trace)
|
124
|
+
def pretty_backtrace backtrace = caller(1)
|
125
|
+
trace = purge_harness_files_from( Array( backtrace ) )
|
126
|
+
expand_symlinks( trace ).join "\n"
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def expand_symlinks backtrace
|
131
|
+
backtrace.collect do |line|
|
132
|
+
file_path, line_num = line.split( ":" )
|
133
|
+
expanded_path = expand_symlink File.expand_path( file_path )
|
134
|
+
expanded_path.to_s + ":" + line_num.to_s
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def purge_harness_files_from backtrace
|
139
|
+
mostly_purged = backtrace.reject do |line|
|
140
|
+
# LOADED_FEATURES is an array of anything `require`d, i.e. everything
|
141
|
+
# but the test in question
|
142
|
+
$LOADED_FEATURES.any? do |require_path|
|
143
|
+
line.include? require_path
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# And remove lines that contain our program name in them
|
148
|
+
completely_purged = mostly_purged.reject {|line| line.include? $0 }
|
149
|
+
end
|
150
|
+
|
151
|
+
# utility method that takes a path as input, checks each component
|
152
|
+
# of the path to see if it is a symlink, and expands
|
153
|
+
# it if it is. returns the expanded path.
|
154
|
+
def expand_symlink file_path
|
155
|
+
file_path.split( "/" ).inject do |full_path, next_dir|
|
156
|
+
next_path = full_path + "/" + next_dir
|
157
|
+
if File.symlink? next_path
|
158
|
+
link = File.readlink next_path
|
159
|
+
next_path =
|
160
|
+
case link
|
161
|
+
when /^\// then link
|
162
|
+
else
|
163
|
+
File.expand_path( full_path + "/" + link )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
next_path
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Vmreverter
|
2
|
+
class Options
|
3
|
+
|
4
|
+
def self.options
|
5
|
+
return @options
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
def self.parse_args
|
10
|
+
return @options if @options
|
11
|
+
|
12
|
+
@no_args = ARGV.empty? ? true : false
|
13
|
+
|
14
|
+
@defaults = {}
|
15
|
+
@options = {}
|
16
|
+
@options_from_file = {}
|
17
|
+
|
18
|
+
optparse = OptionParser.new do|opts|
|
19
|
+
# Set a banner
|
20
|
+
opts.banner = "Usage: #{File.basename($0)} [options...]"
|
21
|
+
|
22
|
+
@defaults[:auth] = File.join(ENV['HOME'], '.fog')
|
23
|
+
opts.on '-a', '--auth FILE',
|
24
|
+
'Use authentication FILE',
|
25
|
+
"Default: #{@defaults[:auth]}" do |file|
|
26
|
+
@options[:auth] = file
|
27
|
+
end
|
28
|
+
|
29
|
+
@defaults[:config] = nil
|
30
|
+
opts.on '-c', '--config FILE',
|
31
|
+
'Use configuration FILE',
|
32
|
+
"Default: #{@defaults[:config]}" do |file|
|
33
|
+
@options[:config] = file
|
34
|
+
end
|
35
|
+
|
36
|
+
@defaults[:options_file] = nil
|
37
|
+
opts.on '-o', '--options-file FILE',
|
38
|
+
'Read options from FILE',
|
39
|
+
'This should evaluate to a ruby hash.',
|
40
|
+
'CLI optons are given precedence.' do |file|
|
41
|
+
@options_from_file = parse_options_file file
|
42
|
+
end
|
43
|
+
|
44
|
+
@defaults[:quiet] = false
|
45
|
+
opts.on '-q', '--[no-]quiet',
|
46
|
+
'Do not log output to STDOUT',
|
47
|
+
'(default: false)' do |bool|
|
48
|
+
@options[:quiet] = bool
|
49
|
+
end
|
50
|
+
|
51
|
+
@defaults[:color] = true
|
52
|
+
opts.on '--[no-]color',
|
53
|
+
'Do not display color in log output',
|
54
|
+
'(default: true)' do |bool|
|
55
|
+
@options[:color] = bool
|
56
|
+
end
|
57
|
+
|
58
|
+
@defaults[:debug] = false
|
59
|
+
opts.on '--[no-]debug',
|
60
|
+
'Enable full debugging',
|
61
|
+
'(default: false)' do |bool|
|
62
|
+
@options[:debug] = bool
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
opts.on_tail("-h","--help","Display this screen") do
|
67
|
+
puts opts
|
68
|
+
exit
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
optparse.parse!
|
73
|
+
|
74
|
+
# We have use the @no_args var because OptParse consumes ARGV as it parses
|
75
|
+
# so we have to check the value of ARGV at the begining of the method,
|
76
|
+
# let the options be set, then output usage.
|
77
|
+
puts optparse if @no_args
|
78
|
+
|
79
|
+
# merge in the options that we read from the file
|
80
|
+
@options = @options_from_file.merge(@options)
|
81
|
+
# merge in defaults
|
82
|
+
@options = @defaults.merge(@options)
|
83
|
+
|
84
|
+
@options
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.parse_options_file(options_file_path)
|
88
|
+
options_file_path = File.expand_path(options_file_path)
|
89
|
+
unless File.exists?(options_file_path)
|
90
|
+
raise ArgumentError, "Specified options file '#{options_file_path}' does not exist!"
|
91
|
+
end
|
92
|
+
# This eval will allow the specified options file to have access to our
|
93
|
+
# scope. It is important that the variable 'options_file_path' is
|
94
|
+
# accessible, because some existing options files (e.g. puppetdb) rely on
|
95
|
+
# that variable to determine their own location (for use in 'require's, etc.)
|
96
|
+
result = eval(File.read(options_file_path))
|
97
|
+
unless result.is_a? Hash
|
98
|
+
raise ArgumentError, "Options file '#{options_file_path}' must return a hash!"
|
99
|
+
end
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Apache Licensed - (github/puppetlabs) ripped from puppet_acceptance. ** See Legal notes
|
2
|
+
# Changes include namespace swaps, method removal, method additions, and complete code refactoring
|
3
|
+
|
4
|
+
module Vmreverter
|
5
|
+
module Shared
|
6
|
+
module ErrorHandler
|
7
|
+
|
8
|
+
def report_and_raise(logger, e, msg)
|
9
|
+
logger.error "Failed: errored in #{msg}"
|
10
|
+
logger.error(e.inspect)
|
11
|
+
bt = e.backtrace
|
12
|
+
logger.pretty_backtrace(bt).each_line do |line|
|
13
|
+
logger.error(line)
|
14
|
+
end
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require "vmreverter/shared/error_handler"
|
4
|
+
rescue LoadError
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'shared', file))
|
6
|
+
end
|
7
|
+
|
8
|
+
module Vmreverter
|
9
|
+
module Shared
|
10
|
+
include Vmreverter::Shared::ErrorHandler
|
11
|
+
end
|
12
|
+
end
|
13
|
+
include Vmreverter::Shared
|
@@ -0,0 +1,60 @@
|
|
1
|
+
%w(hypervisor).each do |lib|
|
2
|
+
begin
|
3
|
+
require "vmreverter/#{lib}"
|
4
|
+
rescue LoadError
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), lib))
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Vmreverter
|
10
|
+
|
11
|
+
HYPERVISOR_TYPES = ['vsphere', 'aws']
|
12
|
+
|
13
|
+
#Collection using Proxy Pattern - Proxy normalized access to specific Hypervisors such as VSphere through creation by the Hypervisor Factory
|
14
|
+
class VMManager
|
15
|
+
|
16
|
+
attr_accessor :hypervisor_collection
|
17
|
+
|
18
|
+
def initialize(config, options, logger)
|
19
|
+
@logger = logger
|
20
|
+
@options = options
|
21
|
+
@hosts = []
|
22
|
+
@config = config
|
23
|
+
@hypervisor_collection = {}
|
24
|
+
@virtual_machines = {}
|
25
|
+
|
26
|
+
@config['HOSTS'].each_key do |name|
|
27
|
+
hypervisor = @config['HOSTS'][name]['hypervisor']
|
28
|
+
@logger.debug "Hypervisor for #{name} is #{hypervisor}"
|
29
|
+
@virtual_machines[hypervisor] = [] unless @virtual_machines[hypervisor]
|
30
|
+
@virtual_machines[hypervisor] << name
|
31
|
+
end
|
32
|
+
|
33
|
+
## Data Model Looks like
|
34
|
+
# @virtual_machines.inspect
|
35
|
+
# {"vsphere" => ["test_server01","test_server02"], "blimpy" => ["aws_test_server01","aws_test_server01"]}
|
36
|
+
|
37
|
+
@virtual_machines.each do |type, names|
|
38
|
+
@hypervisor_collection[type] = Vmreverter::Hypervisor.register(type, names, @options, @config)
|
39
|
+
end
|
40
|
+
|
41
|
+
#return instance created
|
42
|
+
return self
|
43
|
+
end
|
44
|
+
|
45
|
+
def invoke
|
46
|
+
@hypervisor_collection.each do |hypervisor_type, hypervisor_instance|
|
47
|
+
@logger.notify("Invoking #{hypervisor_type} hosts")
|
48
|
+
hypervisor_instance.invoke
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def close_connection
|
53
|
+
@hypervisor_collection.each do |hypervisor_type, hypervisor_instance|
|
54
|
+
@logger.notify("Disconnecting from #{hypervisor_type}")
|
55
|
+
hypervisor_instance.close_connection
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/vmreverter.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require 'rubygems' unless defined?(Gem)
|
6
|
+
|
7
|
+
require 'pry'
|
8
|
+
require 'pry-nav'
|
9
|
+
module Vmreverter
|
10
|
+
$:.unshift(File.dirname(__FILE__))
|
11
|
+
|
12
|
+
# logger
|
13
|
+
require 'vmreverter/logger'
|
14
|
+
|
15
|
+
# Shared methods and helpers
|
16
|
+
require 'vmreverter/shared'
|
17
|
+
|
18
|
+
# hypervisor methods and helpers
|
19
|
+
require 'vmreverter/hypervisor'
|
20
|
+
|
21
|
+
%w( options vmmanager config_tester cli ).each do |lib|
|
22
|
+
begin
|
23
|
+
require "vmreverter/#{lib}"
|
24
|
+
rescue LoadError
|
25
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'vmreverter', lib))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/vmreverter.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require File.expand_path('../lib/vmreverter/version', __FILE__)
|
4
|
+
|
5
|
+
require 'rbconfig'
|
6
|
+
ruby_conf = defined?(RbConfig) ? RbConfig::CONFIG : Config::CONFIG
|
7
|
+
less_than_one_nine = ruby_conf['MAJOR'].to_i == 1 && ruby_conf['MINOR'].to_i < 9
|
8
|
+
|
9
|
+
Gem::Specification.new do |s|
|
10
|
+
s.name = "vmreverter"
|
11
|
+
s.authors = ["shadowbq"]
|
12
|
+
s.email = ["shadowbq@gmail.com"]
|
13
|
+
s.homepage = "https://github.com/shadowbq/vmreverter"
|
14
|
+
s.summary = %q{Revert VM to previous snapshots }
|
15
|
+
s.description = s.summary
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split($\)
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.version = Vmreverter::VERSION
|
22
|
+
s.licenses = ["BSD3", "APACHE2"]
|
23
|
+
|
24
|
+
s.add_development_dependency 'bundler', '~> 1.0'
|
25
|
+
|
26
|
+
# Testing dependencies
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'pry'
|
29
|
+
s.add_development_dependency 'pry-nav'
|
30
|
+
|
31
|
+
|
32
|
+
# Run time dependencies
|
33
|
+
s.add_runtime_dependency 'json'
|
34
|
+
# Fix bug with interdepency of rbnmomi and nokogiri https://github.com/vmware/rbvmomi/issues/31
|
35
|
+
s.add_runtime_dependency 'nokogiri', '1.5.5'
|
36
|
+
s.add_runtime_dependency 'rbvmomi'
|
37
|
+
s.add_runtime_dependency 'blimpy'
|
38
|
+
end
|