vmreverter 0.0.2
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.
- 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
|