vmreverter 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module Vmreverter
2
+ VERSION = '0.0.2'
3
+ end
@@ -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
@@ -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