Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 14 commits
  • 22 files changed
  • 0 commit comments
  • 3 contributors
Commits on Apr 09, 2012
@jcran jcran add snapshot tags 44af15d
Commits on Apr 11, 2012
@jcran jcran remove the nokogiri dep f045191
@jcran jcran tweak the readme f69a282
@jcran jcran more readme updates c7c3639
Commits on Apr 24, 2012
@jcran jcran updated w/ a simple ui 0d54e70
Commits on Apr 25, 2012
@jcran jcran add sinatra as a dep 479882c
@jcran jcran updated to read a config file ce51296
@jcran jcran handle /show 3454075
@jcran jcran ui tweak b051185
Commits on Aug 07, 2012
@jcran-px jcran-px credit where credit is duels 98c1793
@jcran-px jcran-px add a correct README bb2f7cc
Commits on Mar 21, 2013
RageLtMan Add XenApi for Xen/XenServer
Add support for XenAPI via xenapi-ruby gem.
Initial methods scaffolded from VMWare vSphere and Fog

Tested against XenServer6.1 and 6.0.2

Testing procedure:
```
controller = ::Lab::Controllers::VmController.new
controller.build_from_running(opts)
controller[<name from name_label field of a running VM>]
```

TODO: OpenSSL still refuses to accept self-signed certificates.
Would also be nice to rewire XMLRPC::Client to run over Rex::Socket
This may also help to maintain session keepalives and query many
endpoints conrurrently for faster processing.
77332da
Commits on Apr 03, 2013
RageLtMan XenAPI use SSL - resolves self signed cert bug
Ruby's XMLRPC interface does not like self signed certificates.
XMLRPC sessions can be monkeypatched on the fly to ignore SSL
certificates before the initial connection attempt is made. With
XenAPI, the session is created and connected in the same method.
This approach resolves XenAPI upstream SSL validation as well.
c60ad24
Commits on Jun 16, 2013
@jcran jcran Merge pull request #4 from sempervictus/xenapi_driver
Add XenApi for Xen/XenServer
6b0ce82
View
45 Gemfile.lock
@@ -1,18 +1,57 @@
PATH
remote: .
specs:
- lab (0.1.0)
+ lab (0.2.6)
+ fog
net-scp
net-ssh
- nokogiri
+ pry
+ rbvmomi
+ sinatra
GEM
remote: http://rubygems.org/
specs:
+ builder (3.0.0)
+ coderay (1.0.6)
+ excon (0.13.4)
+ fog (1.3.1)
+ builder
+ excon (~> 0.13.0)
+ formatador (~> 0.2.0)
+ mime-types
+ multi_json (~> 1.0)
+ net-scp (~> 1.0.4)
+ net-ssh (>= 2.1.3)
+ nokogiri (~> 1.5.0)
+ ruby-hmac
+ formatador (0.2.1)
+ method_source (0.7.1)
+ mime-types (1.18)
+ multi_json (1.3.2)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-ssh (2.3.0)
- nokogiri (1.5.0)
+ nokogiri (1.5.2)
+ pry (0.9.9.3)
+ coderay (~> 1.0.5)
+ method_source (~> 0.7.1)
+ slop (>= 2.4.4, < 3)
+ rack (1.4.1)
+ rack-protection (1.2.0)
+ rack
+ rbvmomi (1.5.1)
+ builder
+ nokogiri (>= 1.4.1)
+ trollop
+ ruby-hmac (0.4.0)
+ sinatra (1.3.2)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ slop (2.4.4)
+ tilt (1.3.3)
+ trollop (1.16.2)
PLATFORMS
ruby
View
44 README.md
@@ -12,16 +12,20 @@ SUPPORTED VM TECHNOLOGIES:
NOTE: The lab libraries have only been tested with linux as a host, porting to windows is not planned at this time.
Implemented:
- - workstation (Tested against 7.x)
- - remote_workstation (Tested against 7.x)
- - virtualbox (Tested against 4.x)
- - remote_esx (VMware ESX Host Agent 4.1.0 build-348481)
+
+- workstation (Tested against 7.x)
+- remote_workstation (Tested against 7.x)
+- virtualbox (Tested against 4.x)
+- remote_esx (VMware ESX Host Agent 4.1.0 build-348481)
Partially Implemented:
- - amazon_ec2 (via fog gem)
- - dynagen
+
+- amazon_ec2 (via fog gem)
+- dynagen
+- vsphere
Need Implementation:
+
- qemu
- qemudo
- others?
@@ -31,9 +35,18 @@ PLATFORM SUPPORT:
You will need to have this code running on a linux box, Currently this has only been run / tested on Ubuntu 9.04 -> 10.04, though it should run on any linux with an ssh client and the dependencies below. Remote VM Hosts will need to be linux as well, though other platforms may work (untested). If you're interested in porting it to windows, please contact me (jcran).
Platform Dependencies:
- - whatever vm software is necessary for the driver you're using (see SUPPORTED VM TECHNOLOGIES above)
- - net/scp - the gem (net-scp). Required to copy files to/from the devices in the case that tools are not installed. Not necessary if tools are installed.
- - fog - require to use the amazon_ec2 driver
+
+Currently the gem must be run on a linux host with access to the vm
+tech you're automating. For instance, if you want to automate a
+workstation VM, you'll need to run the lab code on the Linux VMWare
+Workstation Host. You can work around this by using the
+remote_workstation driver, which shells (using ssh) into the remote host
+and runs the commands. Note that both systems must be running linux.
+
+CONFIGURING:
+============
+
+Take a look at the example configuration files in config/.
STANDALONE API:
===============
@@ -48,12 +61,12 @@ You must first create a yaml file which describes your vm. See data/lab/test_tar
require 'vm_controller'
vm_controller = ::Lab::Controllers::VmController.new(YAML.load_file(lab_def))
vm_controller['vm1'].start
- vm_controller['vm1'].snapshot("clean")
+ vm_controller['vm1'].create_snapshot("clean")
vm_controller['vm1'].run_command("rm /etc/resolv.conf")
vm_controller['vm1'].open_uri("http://autopwn:8080")
- vm_controller['vm1'].revert("clean")
- vm_controller['vm1'].revert("clean")
+ vm_controller['vm1'].revert_snapshot("clean")
</pre>
+
METASPLOIT MSFCONSOLE LAB PLUGIN:
=================================
@@ -78,3 +91,10 @@ Here's some example usage for the lab plugin.
msf> lab_suspend vm1 // Suspend a vm
msf> lab_revert all snapshot_1 // Revert all vms back to 'snapshot_1'
</pre>
+
+CREDITS:
+========
+
+The lab gem is a collaborative effort, with lots of testing from the Metasploit community, and special thanks to:
+ - Joshua "kernelsmith" Smith - ESXi Support, Tons of fixes & testing
+ - Hauke Mehrtens - VirtualBox Support
View
20 lab.gemspec
@@ -19,20 +19,26 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
- # specify any dependencies here; for example:
- # s.add_development_dependency "rspec"
+ ##
+ ## Dependencies
+ ##
- # ??
- s.add_runtime_dependency "nokogiri"
-
- # Multiple things - fallback execute / copy
+ # Fallback execute / copy in the drivers
s.add_runtime_dependency "net-ssh"
s.add_runtime_dependency "net-scp"
- # Vmware vsphere driver
+ # Powers the vsphere driver
s.add_runtime_dependency "rbvmomi"
+ # Powers the fog driver
+ s.add_runtime_dependency "fog"
+
# util/console.rb
s.add_runtime_dependency "pry"
+ ##
+ ## UI Dependencies
+ ##
+ s.add_runtime_dependency "sinatra"
+
end
View
96 lib/lab/controller/xenapi_controller.rb
@@ -0,0 +1,96 @@
+require 'xenapi-ruby'
+
+module Lab
+module Controllers
+module XenApiController
+
+ # Get vm UUIDs and pull info hashes
+ def self.vm_list_all(user,host,pass,running_only=false)
+ s = self.get_xapi_session(user,host,pass)
+ vms = []
+ s.VM.get_all.map do |vref|
+ vm = s.VM.get_record(vref)
+ next if vm['is_a_template'] or vm['s_a_snapshot'] or vm['name_label'].match(/^Control domain/)
+ vms << vm.merge('vmid' => vref)
+ end
+
+ return running_only ? vms.keep_if {|v| v['power_state'] == 'Running'} : vms
+ end
+
+ # Get SR UUIDs and pull info hashes
+ def self.sr_list_all(user,host,pass)
+ s = self.get_xapi_session(user,host,pass)
+ return s.SR.get_all.map {|srid| s.SR.get_record(srid)}
+ end
+
+ # Compat method wrappers
+
+ def self.running_list(user,host,pass)
+ self.vm_list_all(user,host,pass,true)
+ end
+
+ def self.config_list
+ end
+
+ def self.storage_list
+ end
+
+ def self.hosts_list
+ end
+
+ def self.dir_list
+ end
+
+ private
+
+ def self.get_xapi_session(user,host,pass,port=443)
+ begin
+ # Try SSL with port
+ s = XenAPI::Session.new("https://#{host}:#{port}")
+ s.login_with_password(user,pass)
+ rescue
+ # Miserable HTTP on 80 faildown
+ s = XenAPI::Session.new("http://#{host}")
+ s.login_with_password(user,pass)
+ return s
+ end
+ # If this is somehow nil we will get upstream errors
+ return s
+ end
+
+end
+end
+end
+
+# Lets fix XenAPI's inability to deal with self-signed certs
+# Its actually an XMLRPC problem, not gem specific.
+
+class ::XenAPI::Session
+ def login_with_password(username, password, timeout = 1200,ssl_verify=false)
+ begin
+ @client = XMLRPC::Client.new2(@uri, nil, timeout)
+ if not ssl_verify
+ @client.instance_variable_get(:@http).instance_variable_set(:@verify_mode, OpenSSL::SSL::VERIFY_NONE)
+ end
+ @session = @client.proxy("session")
+
+ response = @session.login_with_password(username, password)
+ raise XenAPI::ErrorFactory.create(*response['ErrorDescription']) unless response['Status'] == 'Success'
+
+ @key = response["Value"]
+
+ #Let's check if it is a working master. It's a small pog due to xen not working as we would like
+ self.pool.get_all
+
+ self
+ rescue Exception => exc
+ error = XenAPI::ErrorFactory.wrap(exc)
+ if @block
+ # returns a new session
+ @block.call(error)
+ else
+ raise error
+ end
+ end
+ end
+end
View
1  lib/lab/controllers.rb
@@ -5,5 +5,6 @@
require 'controller/remote_workstation_controller'
require 'controller/remote_esxi_controller'
require 'controller/vsphere_controller'
+require 'controller/xenapi_controller'
#require 'controller/qemu_controller'
#require 'controller/qemudo_controller'
View
10 lib/lab/driver/remote_esxi_driver.rb
@@ -20,7 +20,7 @@ def initialize(config)
@user = filter_command(config['user'])
@host = filter_command(config['host'])
- @port = config['port']
+ @port = config['port']
end
def start
@@ -47,6 +47,10 @@ def reset
remote_system_command("vim-cmd vmsvc/power.reset #{@vmid}")
end
+ def query_snapshots
+ get_snapshots
+ end
+
def create_snapshot(snapshot)
snapshot = filter_input(snapshot)
@@ -71,8 +75,8 @@ def revert_snapshot(snapshot)
end
end
- # If we got here, the snapshot didn't exist
- raise "Invalid Snapshot Name"
+ # If we got here, we couldn't make it happen
+ raise "Unable to revert"
end
def delete_snapshot(snapshot, remove_children=false)
View
19 lib/lab/driver/virtualbox_driver.rb
@@ -1,5 +1,8 @@
require 'vm_driver'
-require 'nokogiri'
+require 'rexml/document'
+
+include REXML
+
##
## $Id$
@@ -38,16 +41,16 @@ def initialize(config)
def register_and_return_vmid
- xml = Nokogiri::XML(File.new(@location))
- vmid = xml.root.xpath("//Machine[@name]")
+ vmid = nil
+ doc = Document.new(File.open(@location).read)
+ doc.elements.each("//Machine"){|x| vmid = x.attributes['name'] }
- ## only register if we don't already know the vmid
- if !::Lab::Controllers::VirtualBoxController::config_list.include? vmid
- system_command("VBoxManage registervm \"#{@location}\"")
- end
+ #
+ # only register if we don't already know the vmid
+ #
+ system_command("VBoxManage registervm \"#{@location}\"") unless ::Lab::Controllers::VirtualBoxController::config_list.include? vmid
return vmid
-
end
def unregister
View
30 lib/lab/driver/vm_driver.rb
@@ -5,19 +5,19 @@
#
# !!WARNING!! - All drivers are expected to filter input before running
# anything based on it. This is particularly important in the case
-# of the drivers which wrap a command line to provide functionality.
+# of the drivers which wrap a command line to provide functionality.
#
module Lab
module Drivers
class VmDriver
- attr_accessor :vmid
+ attr_accessor :vmid
attr_accessor :location
attr_accessor :os
attr_accessor :tools
attr_accessor :credentials
-
+
def initialize(config)
@vmid = filter_command(config["vmid"].to_s)
@@ -42,7 +42,7 @@ def initialize(config)
def register
raise "Command not Implemented"
end
-
+
def unregister
raise "Command not Implemented"
end
@@ -83,14 +83,14 @@ def delete_snapshot(snapshot)
raise "Command not Implemented"
end
- def run_command(command)
+ def run_command(command)
raise "Command not Implemented"
end
-
+
def copy_from(from, to)
raise "Command not Implemented"
end
-
+
def copy_to(from, to)
raise "Command not Implemented"
end
@@ -137,9 +137,9 @@ def scp_to(local,remote)
puts "DEBUG: uploading #{local} to #{remote}"
scp.upload!(local,remote)
end
- end
+ end
end
-
+
def scp_from(remote, local)
# download a file from a remote server
if @vm_keyfile
@@ -155,7 +155,7 @@ def scp_from(remote, local)
end
end
end
-
+
def ssh_exec(command)
if @vm_keyfile
#puts "DEBUG: authenticating to #{@hostname} as #{@vm_user} with key #{@vm_keyfile}"
@@ -173,8 +173,8 @@ def ssh_exec(command)
def filter_input(string)
return "" unless string # nil becomes empty string
return unless string.class == String # Allow other types unmodified
-
- unless /^[\d\w\s\[\]\{\}\/\\\.\-\"\(\):!]*$/.match string
+
+ unless /^[\:\d\w\s\[\]\{\}\/\\\.\-\"\(\):!]*$/.match string
raise "WARNING! Invalid character in: #{string}"
end
string
@@ -182,9 +182,9 @@ def filter_input(string)
def filter_command(string)
return "" unless string # nil becomes empty string
- return unless string.class == String # Allow other types unmodified
-
- unless /^[\d\w\s\[\]\{\}\/\\\.\-\"\(\)]*$/.match string
+ return unless string.class == String # Allow other types unmodified
+
+ unless /^[\:\d\w\s\[\]\{\}\/\\\.\-\"\(\)]*$/.match string
raise "WARNING! Invalid character in: #{string}"
end
string
View
150 lib/lab/driver/xenapi_driver.rb
@@ -0,0 +1,150 @@
+require 'vm_driver'
+
+##
+## $Id$
+##
+
+# This driver was built against:
+# XenServer 6.1 and 6.0.2
+
+module Lab
+module Drivers
+
+class XenApiDriver < VmDriver
+
+
+ def initialize(config)
+ unless config['user'] then raise ArgumentError, "Must provide a username" end
+ unless config['host'] then raise ArgumentError, "Must provide a hostname" end
+ unless config['pass'] then raise ArgumentError, "Must provide a password" end
+ super(config)
+
+ # Load XenAPI
+ begin
+ require 'xenapi-ruby'
+ rescue LoadError
+ raise "WARNING: Library xenapi-ruby not found. Could not create driver!"
+ end
+
+ @user = filter_command(config['user'])
+ @host = filter_command(config['host'])
+ @pass = config['pass']
+ @port = filter_input(config['port']) || nil
+
+ @vm = ensure_xen_session.VM.get_record(@vmid) or fail "VM not found"
+ end
+
+ def start
+ ensure_xen_session.VM.start @vmid
+ end
+
+ def stop
+ ensure_xen_session.VM.stop @vmid
+ end
+
+ def suspend
+ ensure_xen_session.VM.susupend @vmid
+ end
+
+ def pause
+ ensure_xen_session.VM.pause @vmid
+ end
+
+ def resume
+ ensure_xen_session.VM.resume @vmid
+ end
+
+ def reset
+ ensure_xen_session.VM.power_state_reset @vmid
+ end
+
+ def create_snapshot(snapshot)
+ snapshot = filter_input(snapshot)
+ begin
+ snap_res = ensure_xen_session.VM.snapshot_with_quiesce(@vmid,snapshot)
+ rescue
+ snap_res = ensure_xen_session.VM.snapshot(@vmid,snapshot)
+ end
+ return snap_res
+ end
+
+ def revert_snapshot(snapshot)
+ snapshot = filter_input(snapshot)
+ ensure_xen_session.VM.revert(snapshot)
+ end
+
+ def delete_snapshot(snapshot, remove_children=false)
+ raise "Unimplemented"
+ # If we got here, the snapshot didn't exist
+ raise "Invalid Snapshot Name"
+ end
+
+ def delete_all_snapshots
+ raise "Unimplemented"
+ end
+
+ def run_command(command)
+ raise "Unimplemented"
+ end
+
+ def copy_from(from, to)
+ if @os == "linux"
+ scp_from(from, to)
+ else
+ raise "Unimplemented"
+ end
+ end
+
+ def copy_to(from, to)
+ if @os == "linux"
+ scp_to(from, to)
+ else
+ raise "Unimplemented"
+ end
+ end
+
+ def check_file_exists(file)
+ raise "Unimplemented"
+ end
+
+ def create_directory(directory)
+ raise "Unimplemented"
+ end
+
+ def cleanup
+ raise "Unimplemented"
+ end
+
+ def running?
+ raise "Unimplemented"
+ end
+
+ def xen_info_hash
+ @vm
+ end
+
+ def ensure_xen_session
+ # HTTP/HTTPS XMLRPC session and auth
+ # This block ensures the session lives and we dont keep starting new ones
+ begin
+ @xen_session.VM.get_all
+ rescue
+ @xen_session = Lab::Controllers::XenApiController.get_xapi_session(
+ @user,
+ @host,
+ @pass,
+ @port
+ )
+ end
+ @xen_session
+ end
+
+ # Pass VM.get_all to test
+ def raw_api_call(sttring)
+ eval "ensure_xen_session.#{string}"
+ end
+
+end
+
+end
+end
View
1  lib/lab/drivers.rb
@@ -5,5 +5,6 @@
require 'driver/remote_workstation_driver'
require 'driver/remote_esxi_driver'
require 'driver/vsphere_driver'
+require 'driver/xenapi_driver'
#require 'driver/qemu_driver'
#require 'driver/qemudo_driver'
View
2  lib/lab/version.rb
@@ -1,3 +1,3 @@
module Lab
- VERSION = "0.2.3"
+ VERSION = "0.2.6"
end
View
99 lib/lab/vm.rb
@@ -4,7 +4,7 @@
module Lab
class Vm
-
+
attr_accessor :vmid
attr_accessor :hostname
attr_accessor :description
@@ -15,9 +15,11 @@ class Vm
attr_accessor :credentials
attr_accessor :tools
attr_accessor :type
+ attr_accessor :notes
attr_accessor :os
attr_accessor :arch
- attr_accessor :tags
+ attr_accessor :machine_tags
+ attr_accessor :snapshots
## Initialize takes a vm configuration hash of the form
## - vmid (unique id)
@@ -34,18 +36,19 @@ class Vm
## os (currently linux / windows / solaris / aix) - may be used in modifiers
## arch (currently 32 / 64)
## modifiers - can be anything in the modifiers directory
- ## tags - list of strings associated with this vm
-
- def initialize(config = {})
+ ## machine_tags - list of strings associated with the machine (not individual snapshots)
+ ## snapshots - list of snapshots
+
+ def initialize(config = {})
# TODO - This is a mess. clean up, and pass stuff down to drivers
- # and then rework the code that uses this api.
- @vmid = config['vmid'].to_s
- raise "Invalid VMID" unless @vmid
+ # and then rework the code that uses this api.
+ @vmid = config['vmid'].to_s
+ raise "Invalid VMID" unless @vmid and @vmid.strip.length > 0
# Grab the hostname if specified, otherwise use the vmid
# VMID will be different in the case of ESX
- @hostname = config['hostname']
+ @hostname = config['hostname'] || config['name_label']
if !@hostname
@hostname = @vmid
end
@@ -54,13 +57,14 @@ def initialize(config = {})
@driver_type.downcase!
@location = filter_input(config['location'])
- @description = config['description']
+ @description = config['description'] || config['name_description']
+ @notes = config['notes']
@tools = config['tools']
@os = config['os']
@arch = config['arch']
@type = filter_input(config['type']) || "unspecified"
@credentials = config['credentials'] || []
-
+
# TODO - Currently only implemented for the first set
if @credentials.count > 0
@vm_user = filter_input(@credentials[0]['user']) || "\'\'"
@@ -90,11 +94,13 @@ def initialize(config = {})
elsif @driver_type == "fog"
@driver = Lab::Drivers::FogDriver.new(config, config['fog_config'])
elsif @driver_type == "dynagen"
- @driver = Lab::Drivers::DynagenDriver.new(config, config['dynagen_config'])
+ @driver = Lab::Drivers::DynagenDriver.new(config, config['dynagen_config'])
elsif @driver_type == "remote_esxi"
@driver = Lab::Drivers::RemoteEsxiDriver.new(config)
elsif @driver_type == "vsphere"
@driver = Lab::Drivers::VsphereDriver.new(config)
+ elsif @driver_type == "xenapi"
+ @driver = Lab::Drivers::XenApiDriver.new(config)
#elsif @driver_type == "qemu"
# @driver = Lab::Drivers::QemuDriver.new
#elsif @driver_type == "qemudo"
@@ -102,29 +108,52 @@ def initialize(config = {})
else
raise "Unknown Driver Type"
end
-
+
# Load in a list of modifiers. These provide additional methods
- # Currently it is up to the user to verify that
+ # Currently it is up to the user to verify that
# modifiers are properly used with the correct VM image.
#
# If not, the results are likely to be disasterous.
@modifiers = config['modifiers']
-
- if @modifiers
+
+ if @modifiers
begin
@modifiers.each { |modifier| self.class.send(:include, eval("Lab::Modifier::#{modifier}"))}
rescue Exception => e
# modifier likely didn't exist
end
end
-
+
#
- # Grab a tags array
+ # Grab a list of snapshots & tags associated with this machine
#
- @tags = config['tags']
-
+ # machine_tags:
+ # - ie6
+ # - ie7
+ # - ie8
+ # - firefox
+ # snapshots:
+ # - ie6:
+ # - snapshot_tags:
+ # - ie6
+ # - ie7:
+ # - snapshot_tags:
+ # - ie7
+ # - ie8:
+ # - snapshot_tags:
+ # - ie8
+ # - bap:
+ # - snapshot_tags:
+ # - flash_10.2.153.1
+ # - reader_9.3.3
+ # - java_6u23
+ # - quicktime_player_7.6.9
+ #
+ @machine_tags = config['machine_tags']
+ @snapshots = config['snapshots']
+
end
-
+
def running?
@driver.running?
end
@@ -148,15 +177,19 @@ def pause
def suspend
@driver.suspend
end
-
+
def reset
@driver.reset
end
-
+
def resume
@driver.resume
end
+ def query_snapshots
+ @driver.query_snapshots
+ end
+
def create_snapshot(snapshot)
@driver.create_snapshot(snapshot)
end
@@ -177,11 +210,11 @@ def revert_and_start(snapshot)
def copy_to(from,to)
@driver.copy_to(from,to)
end
-
+
def copy_from(from,to)
@driver.copy_from(from,to)
end
-
+
def run_command(command)
@driver.run_command(command)
end
@@ -189,13 +222,13 @@ def run_command(command)
def check_file_exists(file)
@driver.check_file_exists(file)
end
-
+
def create_directory(directory)
@driver.create_directory(directory)
end
def open_uri(uri)
- # we don't filter the uri, as it's getting tossed into a script
+ # we don't filter the uri, as it's getting tossed into a script
# by the driver
if @os == "windows"
command = "\"C:\\program files\\internet explorer\\iexplore.exe\" #{uri}"
@@ -211,9 +244,9 @@ def to_s
end
def to_yaml
-
+
# TODO - push this down to the drivers.
-
+
# Standard configuration options
out = " - vmid: #{@vmid}\n"
out += " hostname: #{@hostname}\n"
@@ -224,7 +257,7 @@ def to_yaml
out += " tools: #{@tools}\n"
out += " os: #{@os}\n"
out += " arch: #{@arch}\n"
-
+
if @user or @host # Remote vm/drivers only
out += " user: #{@user}\n"
out += " host: #{@host}\n"
@@ -245,11 +278,11 @@ def to_yaml
end
out += " credentials:\n"
- @credentials.each do |credential|
+ @credentials.each do |credential|
out += " - user: #{credential['user']}\n"
out += " pass: #{credential['pass']}\n"
end
-
+
return out
end
private
@@ -257,7 +290,7 @@ def to_yaml
def filter_input(string)
return "" unless string # nil becomes empty string
return unless string.class == String # Allow other types
-
+
unless /^[(!)\d*\w*\s*\[\]\{\}\/\\\.\-\"\(\)]*$/.match string
raise "WARNING! Invalid character in: #{string}"
end
View
93 lib/lab/vm_controller.rb
@@ -1,8 +1,8 @@
#
# $Id$
#
-# This is the main lab controller. Require this controller to get all
-# lab functionality.
+# This is the main lab controller. Require this controller to get all
+# lab functionality.
#
#
@@ -26,27 +26,28 @@
module Lab
module Controllers
- class VmController
+ class VmController
include Enumerable
include Lab::Controllers::WorkstationController
include Lab::Controllers::RemoteWorkstationController
- include Lab::Controllers::VirtualBoxController
+ include Lab::Controllers::VirtualBoxController
include Lab::Controllers::FogController
- include Lab::Controllers::DynagenController
+ include Lab::Controllers::DynagenController
include Lab::Controllers::RemoteEsxiController
include Lab::Controllers::VsphereController
- #include Lab::Controllers::QemuController
- #include Lab::Controllers::QemudoController
+ include Lab::Controllers::XenApiController
+ #include Lab::Controllers::QemuController
+ #include Lab::Controllers::QemudoController
def initialize (labdef=nil)
# Start with an empty array of vm objects
- @vms = []
+ @vms = []
# labdef is a just a big array of hashes
load_vms(labdef) if labdef
end
-
+
def clear!
@vms = []
end
@@ -73,7 +74,7 @@ def find_by_hostname(search)
def find_by_tag(search)
@vms.each do |vm|
- vm.tags.each do |tag|
+ vm.tags.each do |tag|
return vm if tag == search
end
end
@@ -82,9 +83,9 @@ def find_by_tag(search)
def add_vm(vmid, location=nil, os=nil, tools=nil, credentials=nil, user=nil, host=nil)
@vms << Vm.new( {
- 'vmid' => vmid,
- 'driver' => type,
- 'location' => location,
+ 'vmid' => vmid,
+ 'driver' => type,
+ 'location' => location,
'credentials' => credentials,
'user' => user,
'host' => host
@@ -93,7 +94,7 @@ def add_vm(vmid, location=nil, os=nil, tools=nil, credentials=nil, user=nil, hos
def remove_by_vmid(vmid)
@vms.delete(self.find_by_vmid(vmid))
- end
+ end
def from_file(file)
load_vms(YAML::load_file(file))
@@ -107,7 +108,7 @@ def load_vms(vms)
end
def to_file(file)
- File.open(file, 'w') { |f| @vms.each { |vm| f.puts vm.to_yaml } }
+ File.open(file, 'w') { |f| @vms.each { |vm| f.puts vm.to_yaml } }
end
def each &block
@@ -144,22 +145,24 @@ def build_from_dir(driver_type, dir, clear=false)
if driver_type.downcase == "workstation"
vm_list = ::Lab::Controllers::WorkstationController::dir_list(dir)
- elsif driver_type.downcase == "remote_workstation"
+ elsif driver_type.downcase == "remote_workstation"
vm_list = ::Lab::Controllers::RemoteWorkstationController::dir_list(dir)
- elsif driver_type.downcase == "virtualbox"
+ elsif driver_type.downcase == "virtualbox"
vm_list = ::Lab::Controllers::VirtualBoxController::dir_list(dir)
elsif driver_type.downcase == "fog"
vm_list = ::Lab::Controllers::FogController::dir_list(dir)
- elsif driver_type.downcase == "Dynagen"
+ elsif driver_type.downcase == "Dynagen"
vm_list = ::Lab::Controllers::DynagenController::dir_list(dir)
elsif driver_type.downcase == "remote_esxi"
vm_list =::Lab::Controllers::RemoteEsxiController::dir_list(dir)
elsif driver_type.downcase == "vsphere"
vm_list =::Lab::Controllers::VsphereController::dir_list(dir)
+ elsif driver_type.downcase == "xenapi"
+ vm_list =::Lab::Controllers::XenApiController::dir_list(dir)
else
raise TypeError, "Unsupported VM Type"
end
-
+
vm_list.each_index do |index|
@vms << Vm.new( {'vmid' => "vm_#{index}", 'driver' => driver_type, 'location' => vm_list[index]} )
end
@@ -167,11 +170,11 @@ def build_from_dir(driver_type, dir, clear=false)
#
- # Builds a vm lab from all running vms. Handy for connecting and saving out
+ # Builds a vm lab from all running vms. Handy for connecting and saving out
# a config or just managing the currently running vms
#
def build_from_running(driver_type=nil, user=nil, host=nil, clear=false, pass=nil)
-
+
if clear
@vms = []
end
@@ -185,7 +188,7 @@ def build_from_running(driver_type=nil, user=nil, host=nil, clear=false, pass=ni
# Add it to the vm list
@vms << Vm.new({
'vmid' => "vm_#{index}",
- 'driver' => driver_type,
+ 'driver' => driver_type,
'location' => item
})
end
@@ -197,20 +200,20 @@ def build_from_running(driver_type=nil, user=nil, host=nil, clear=false, pass=ni
# Add it to the VM list
@vms << Vm.new({
'vmid' => "vm_#{index}",
- 'driver' => driver_type,
- 'location' => item,
+ 'driver' => driver_type,
+ 'location' => item,
'user' => user,
- 'host' => host
+ 'host' => host
})
end
when :virtualbox
vm_list = ::Lab::Controllers::VirtualBoxController::running_list
vm_list.each do |item|
# Add it to the vm list
- @vms << Vm.new( {
+ @vms << Vm.new( {
'vmid' => "#{item}",
'driver' => driver_type,
- 'location' => nil
+ 'location' => nil
})
end
when :fog
@@ -220,31 +223,41 @@ def build_from_running(driver_type=nil, user=nil, host=nil, clear=false, pass=ni
when :remote_esxi
vm_list = ::Lab::Controllers::RemoteEsxiController::running_list(user,host)
vm_list.each do |item|
- @vms << Vm.new( {
+ @vms << Vm.new( {
'vmid' => "#{item[:id]}",
'name' => "#{item[:name]}",
- 'driver' => driver_type,
+ 'driver' => driver_type,
'user' => user,
- 'host' => host
+ 'host' => host
})
end
when :vsphere
vm_list = ::Lab::Controllers::VsphereController::running_list(user,host,pass)
vm_list.each do |item|
- @vms << Vm.new( {
+ @vms << Vm.new( {
'vmid' => "#{item[:id]}",
'name' => "#{item[:name]}",
- 'driver' => driver_type,
+ 'driver' => driver_type,
'user' => user,
'host' => host,
'pass' => pass
})
end
+ when :xenapi
+ vm_list = ::Lab::Controllers::XenApiController::running_list(user,host,pass)
+ vm_list.each do |item|
+ @vms << Vm.new( item.merge({
+ 'driver' => driver_type,
+ 'user' => user,
+ 'host' => host,
+ 'pass' => pass
+ }))
+ end
else
raise TypeError, "Unsupported VM Type"
end
- end
+ end
#
# Applicable only to virtualbox. Reads the config file & parses / creates
@@ -258,28 +271,28 @@ def build_from_config(driver_type=nil, user=nil, host=nil, clear=false)
case driver_type.intern
when :virtualbox
vm_list = ::Lab::Controllers::VirtualBoxController::config_list
-
+
vm_list.each do |item|
# Add it to the vm list
- @vms << Vm.new( {
+ @vms << Vm.new( {
'vmid' => "#{item}",
- 'driver' => driver_type,
- 'location' => nil,
+ 'driver' => driver_type,
+ 'location' => nil,
'user' => user,
'host' => host } )
end
-
+
else
raise TypeError, "Unsupported VM Type"
end
- end
+ end
def running?(vmid)
if includes_vmid?(vmid)
return self.find_by_vmid(vmid).running?
end
- return false
+ return false
end
end
end
View
1  ui/README
@@ -0,0 +1 @@
+This is a simple ui for vms managed by the lab gem.
View
13 ui/config.ru
@@ -0,0 +1,13 @@
+require './labmon'
+require './helpers'
+
+root_dir = File.dirname(__FILE__)
+
+set :environment, :production
+set :root, root_dir
+set :app_file, File.join(root_dir, 'labmon.rb')
+set :public_folder, root_dir + '/public'
+set :logging, true
+set :run, false
+
+run Sinatra::Application
View
21 ui/helpers.rb
@@ -0,0 +1,21 @@
+require 'sinatra/base'
+
+module Sinatra
+ module HTMLEscapeHelper
+ def h(text)
+ Rack::Utils.escape_html(text)
+ end
+ end
+ helpers HTMLEscapeHelper
+
+ module InputBouncer
+ def verify_as_ip_or_hostname(param)
+ return unless param # possible this could be called on a nil parameter
+ # First pass scan for anything not alphanum or . or -
+ redirect "/exception" if param =~ /[^a-zA-Z0-9\x2e\x2d]/
+ # Now test for nice formatting.
+ redirect "/exception" if !(param =~ /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$/)
+ end
+ end
+ register InputBouncer
+end
View
56 ui/labmon.rb
@@ -0,0 +1,56 @@
+require 'sinatra'
+require 'lab'
+
+before do
+ ## Basic blacklisting of metacharacters
+ redirect to "/exception" if request.path_info =~ /\;|\|/
+ @controller = Lab::Controllers::VmController.new
+ if File.exist? "config.txt"
+ @controller.from_file(File.open("config.txt").read.strip)
+ end
+end
+
+get '/' do
+ redirect to "/list"
+end
+
+get '/exception' do
+ "sorry, that's not allowed, request contains bad data"
+end
+
+get '/list' do
+ erb :list
+end
+
+get '/show' do
+ redirect to '/list'
+end
+
+get '/show/:hostname' do
+ # Get the watcher
+ hostname = params[:hostname]
+ @vm = @controller[hostname]
+ erb :show
+end
+
+get '/start/:hostname' do
+ hostname = params[:hostname]
+ @vm = @controller[hostname]
+ @vm.start
+ redirect to "/show/#{hostname}"
+end
+
+get '/stop/:hostname' do
+ hostname = params[:hostname]
+ @vm = @controller[hostname]
+ @vm.stop
+ redirect to "/show/#{hostname}"
+end
+
+get '/revert_snapshot/:hostname' do
+ hostname = params[:hostname]
+ snapshot = params[:snapshot] || "clean"
+ @vm = @controller[hostname]
+ @vm.revert_snapshot snapshot
+ redirect to "/show/#{hostname}"
+end
View
0  ui/log/.gitkeep
No changes.
View
1  ui/views/index.erb
@@ -0,0 +1 @@
+<h1> LabMon </h1>
View
5 ui/views/layout.erb
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <%= yield %>
+ </body>
+</html>
View
7 ui/views/list.erb
@@ -0,0 +1,7 @@
+<h1> LabMon </h1>
+
+<ul>
+ <% @controller.each do |vm| %>
+ <li> <a href=show/<%=vm.hostname%>><%=vm.hostname%></a> - <%= vm.description %> </li>
+ <% end %>
+</ul>
View
31 ui/views/show.erb
@@ -0,0 +1,31 @@
+<h1> LabMon </h1>
+
+<ul>
+<li> Hostname: <%= @vm.hostname %> </ll>
+<li> Description: <%= @vm.description %> </li>
+<li> Notes: <%= @vm.notes %> </li>
+<li> Running: <%= @vm.running? %> </li>
+<li> Tags: <%= @vm.machine_tags.join(", ") unless @vm.snapshots.nil? %></li>
+<li> Snapshots: <%= @vm.snapshots.join(", ") unless @vm.snapshots.nil? %></li>
+</ul>
+
+<table>
+<tr>
+<td>
+<form name=start_vm action="/start/<%=@vm.hostname%>">
+ <input type=submit value="Start">
+</form>
+</td>
+<td>
+<form name=stop_vm action="/stop/<%=@vm.hostname%>">
+ <input type=submit value="Stop">
+</form>
+</td>
+<td>
+<form name=revert_snapshot action="/revert_snapshot/<%=@vm.hostname%>">
+ <input type=text value="clean">
+ <input type=submit value="Revert">
+</form>
+<td>
+</tr>
+</table

No commit comments for this range

Something went wrong with that request. Please try again.