fission 0.3.0 → 0.4.0.beta.1

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.
Files changed (50) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +1 -1
  4. data/lib/fission.rb +5 -6
  5. data/lib/fission/cli.rb +77 -7
  6. data/lib/fission/command.rb +43 -1
  7. data/lib/fission/command/clone.rb +19 -20
  8. data/lib/fission/command/delete.rb +29 -25
  9. data/lib/fission/command/snapshot_create.rb +11 -26
  10. data/lib/fission/command/snapshot_list.rb +13 -19
  11. data/lib/fission/command/snapshot_revert.rb +11 -26
  12. data/lib/fission/command/start.rb +11 -25
  13. data/lib/fission/command/status.rb +26 -10
  14. data/lib/fission/command/stop.rb +10 -21
  15. data/lib/fission/command/suspend.rb +21 -21
  16. data/lib/fission/command_helpers.rb +21 -0
  17. data/lib/fission/config.rb +35 -0
  18. data/lib/fission/fusion.rb +11 -3
  19. data/lib/fission/lease.rb +148 -0
  20. data/lib/fission/metadata.rb +55 -2
  21. data/lib/fission/response.rb +76 -0
  22. data/lib/fission/ui.rb +49 -0
  23. data/lib/fission/version.rb +1 -1
  24. data/lib/fission/vm.rb +653 -75
  25. data/spec/contexts/command.rb +12 -0
  26. data/spec/fission/cli_spec.rb +4 -11
  27. data/spec/fission/command/clone_spec.rb +45 -45
  28. data/spec/fission/command/delete_spec.rb +56 -43
  29. data/spec/fission/command/snapshot_create_spec.rb +29 -51
  30. data/spec/fission/command/snapshot_list_spec.rb +25 -26
  31. data/spec/fission/command/snapshot_revert_spec.rb +27 -53
  32. data/spec/fission/command/start_spec.rb +25 -69
  33. data/spec/fission/command/status_spec.rb +48 -13
  34. data/spec/fission/command/stop_spec.rb +25 -42
  35. data/spec/fission/command/suspend_spec.rb +54 -49
  36. data/spec/fission/command_helpers_spec.rb +30 -0
  37. data/spec/fission/command_spec.rb +19 -0
  38. data/spec/fission/config_spec.rb +24 -0
  39. data/spec/fission/fusion_spec.rb +6 -6
  40. data/spec/fission/lease_spec.rb +176 -0
  41. data/spec/fission/metadata_spec.rb +8 -8
  42. data/spec/fission/response_spec.rb +81 -0
  43. data/spec/fission/vm_spec.rb +869 -193
  44. data/spec/fission_spec.rb +0 -6
  45. data/spec/helpers/command_helpers.rb +12 -0
  46. data/spec/helpers/response_helpers.rb +21 -0
  47. data/spec/matchers/be_a_successful_response.rb +7 -0
  48. data/spec/matchers/be_an_unsuccessful_response.rb +10 -0
  49. data/spec/spec_helper.rb +7 -0
  50. metadata +24 -5
@@ -1,37 +1,31 @@
1
1
  module Fission
2
2
  class Command
3
3
  class SnapshotList < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 1
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ""
13
- Fission.ui.output_and_exit "Incorrect arguments for snapshot list command", 1
14
- end
7
+ incorrect_arguments 'snapshot list' unless @args.count == 1
15
8
 
16
- vm_name = @args.first
9
+ vm = VM.new @args.first
17
10
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
11
+ response = vm.snapshots
21
12
 
22
- @vm = Fission::VM.new vm_name
23
- snaps = @vm.snapshots
13
+ if response.successful?
14
+ snaps = response.data
24
15
 
25
- if snaps.any?
26
- Fission.ui.output snaps.join("\n")
16
+ if snaps.any?
17
+ output snaps.join("\n")
18
+ else
19
+ output "No snapshots found for VM '#{vm.name}'"
20
+ end
27
21
  else
28
- Fission.ui.output "No snapshots found for VM '#{vm_name}'"
22
+ output_and_exit "There was an error listing the snapshots. The error was:\n#{response.message}", response.code
29
23
  end
30
24
  end
31
25
 
32
26
  def option_parser
33
27
  optparse = OptionParser.new do |opts|
34
- opts.banner = "\nsnapshot list: fission snapshot list my_vm"
28
+ opts.banner = "\nsnapshot list: fission snapshot list vm_name"
35
29
  end
36
30
 
37
31
  optparse
@@ -1,42 +1,27 @@
1
1
  module Fission
2
2
  class Command
3
3
  class SnapshotRevert < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 2
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ''
13
- Fission.ui.output_and_exit 'Incorrect arguments for snapshot revert command', 1
14
- end
15
-
16
- vm_name, snap_name = @args.take 2
7
+ incorrect_arguments 'snapshot revert' unless @args.count == 2
17
8
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
21
-
22
- if Fission::Fusion.is_running?
23
- Fission.ui.output 'It looks like the Fusion GUI is currently running'
24
- Fission.ui.output_and_exit 'Please exit the Fusion GUI and try again', 1
25
- end
9
+ vm = VM.new @args[0]
10
+ snap_name = @args[1]
26
11
 
27
- @vm = Fission::VM.new vm_name
12
+ output "Reverting to snapshot '#{snap_name}'"
13
+ response = vm.revert_to_snapshot snap_name
28
14
 
29
- unless @vm.snapshots.include? snap_name
30
- Fission.ui.output_and_exit "Unable to find the snapshot '#{snap_name}'", 1
15
+ if response.successful?
16
+ output "Reverted to snapshot '#{snap_name}'"
17
+ else
18
+ output_and_exit "There was an error reverting to the snapshot. The error was:\n#{response.message}", response.code
31
19
  end
32
-
33
- Fission.ui.output "Reverting to snapshot '#{snap_name}'"
34
- @vm.revert_to_snapshot(snap_name)
35
20
  end
36
21
 
37
22
  def option_parser
38
23
  optparse = OptionParser.new do |opts|
39
- opts.banner = "\nsnapshot revert: fission snapshot revert my_vm snapshot_1"
24
+ opts.banner = "\nsnapshot revert: fission snapshot revert vm_name snapshot_1"
40
25
  end
41
26
 
42
27
  optparse
@@ -1,6 +1,7 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Start < Command
4
+ include CommandHelpers
4
5
 
5
6
  def initialize(args=[])
6
7
  super
@@ -10,42 +11,27 @@ module Fission
10
11
  def execute
11
12
  option_parser.parse! @args
12
13
 
13
- if @args.empty?
14
- Fission.ui.output self.class.help
15
- Fission.ui.output ""
16
- Fission.ui.output_and_exit "Incorrect arguments for start command", 1
17
- end
14
+ incorrect_arguments 'start' if @args.empty?
18
15
 
19
- vm_name = @args.first
16
+ vm = VM.new @args.first
20
17
 
21
- unless Fission::VM.exists? vm_name
22
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
23
- end
18
+ output "Starting '#{vm.name}'"
19
+ start_args = {}
24
20
 
25
- if VM.all_running.include?(vm_name)
26
- Fission.ui.output ''
27
- Fission.ui.output_and_exit "VM '#{vm_name}' is already running", 0
28
- end
21
+ start_args[:headless] = true if @options.headless
29
22
 
30
- Fission.ui.output "Starting '#{vm_name}'"
31
- @vm = Fission::VM.new vm_name
23
+ response = vm.start start_args
32
24
 
33
- if @options.headless
34
- if Fission::Fusion.is_running?
35
- Fission.ui.output 'It looks like the Fusion GUI is currently running'
36
- Fission.ui.output 'A VM cannot be started in headless mode when the Fusion GUI is running'
37
- Fission.ui.output_and_exit "Exit the Fusion GUI and try again", 1
38
- else
39
- @vm.start :headless => true
40
- end
25
+ if response.successful?
26
+ output "VM '#{vm.name}' started"
41
27
  else
42
- @vm.start
28
+ output_and_exit "There was a problem starting the VM. The error was:\n#{response.message}", response.code
43
29
  end
44
30
  end
45
31
 
46
32
  def option_parser
47
33
  optparse = OptionParser.new do |opts|
48
- opts.banner = "\nstart usage: fission start vm [options]"
34
+ opts.banner = "\nstart usage: fission start vm_name [options]"
49
35
 
50
36
  opts.on '--headless', 'Start the VM in headless mode (i.e. no Fusion GUI console)' do
51
37
  @options.headless = true
@@ -2,19 +2,35 @@ module Fission
2
2
  class Command
3
3
  class Status < Command
4
4
 
5
- def initialize(args=[])
6
- super
7
- end
8
-
9
5
  def execute
10
- all_vms = VM.all
11
- all_running_vms = VM.all_running
6
+ response = VM.all
7
+ if response.successful?
8
+ all_vms = response.data
9
+ end
10
+
11
+ response = VM.all_running
12
+ if response.successful?
13
+ all_running_vm_names = response.data.collect { |v| v.name }
14
+ else
15
+ output_and_exit "There was an error getting the list of running VMs. The error was:\n#{response.message}", response.code
16
+ end
17
+
18
+ vm_names = all_vms.collect { |v| v.name }
19
+
20
+ longest_vm_name = vm_names.max { |a,b| a.length <=> b.length }
12
21
 
13
- longest_vm_name = all_vms.max { |a,b| a.length <=> b.length }
22
+ all_vms.each do |vm|
23
+ if all_running_vm_names.include? vm.name
24
+ status = '[running]'
25
+ else
26
+ if vm.suspend_file_exists?
27
+ status = '[suspended]'
28
+ else
29
+ status = '[not running]'
30
+ end
31
+ end
14
32
 
15
- VM.all.each do |vm|
16
- status = all_running_vms.include?(vm) ? '[running]' : '[not running]'
17
- Fission.ui.output_printf "%-#{longest_vm_name.length}s %s\n", vm, status
33
+ output_printf "%-#{longest_vm_name.length}s %s\n", vm.name, status
18
34
  end
19
35
  end
20
36
 
@@ -1,37 +1,26 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Stop < Command
4
-
5
- def initialize(args=[])
6
- super
7
- end
4
+ include Fission::CommandHelpers
8
5
 
9
6
  def execute
10
- unless @args.count == 1
11
- Fission.ui.output self.class.help
12
- Fission.ui.output ""
13
- Fission.ui.output_and_exit "Incorrect arguments for stop command", 1
14
- end
7
+ incorrect_arguments 'stop' unless @args.count == 1
15
8
 
16
- vm_name = @args.first
9
+ vm = VM.new @args.first
17
10
 
18
- unless Fission::VM.exists? vm_name
19
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
20
- end
11
+ output "Stopping '#{vm.name}'"
12
+ response = vm.stop
21
13
 
22
- unless VM.all_running.include?(vm_name)
23
- Fission.ui.output ''
24
- Fission.ui.output_and_exit "VM '#{vm_name}' is not running", 0
14
+ if response.successful?
15
+ output "VM '#{vm.name}' stopped"
16
+ else
17
+ output_and_exit "There was an error stopping the VM. The error was:\n#{response.message}", response.code
25
18
  end
26
-
27
- Fission.ui.output "Stopping '#{vm_name}'"
28
- @vm = Fission::VM.new vm_name
29
- @vm.stop
30
19
  end
31
20
 
32
21
  def option_parser
33
22
  optparse = OptionParser.new do |opts|
34
- opts.banner = "\nstop usage: fission stop vm"
23
+ opts.banner = "\nstop usage: fission stop vm_name"
35
24
  end
36
25
 
37
26
  optparse
@@ -1,6 +1,7 @@
1
1
  module Fission
2
2
  class Command
3
3
  class Suspend < Command
4
+ include Fission::CommandHelpers
4
5
 
5
6
  def initialize(args=[])
6
7
  super
@@ -10,40 +11,39 @@ module Fission
10
11
  def execute
11
12
  option_parser.parse! @args
12
13
 
13
- if @args.count != 1 && !@options.all
14
- Fission.ui.output self.class.help
15
- Fission.ui.output ""
16
- Fission.ui.output_and_exit "Incorrect arguments for suspend command", 1
17
- end
14
+ incorrect_arguments 'suspend' if @args.count != 1 && !@options.all
15
+
16
+ vms_to_suspend.each do |vm|
17
+ output "Suspending '#{vm.name}'"
18
+ response = vm.suspend
18
19
 
19
- vms_to_suspend.each do |vm_name|
20
- Fission.ui.output "Suspending '#{vm_name}'"
21
- Fission::VM.new(vm_name).suspend
20
+ if response.successful?
21
+ output "VM '#{vm.name}' suspended"
22
+ else
23
+ output_and_exit "There was an error suspending the VM. The error was:\n#{response.message}", response.code
24
+ end
22
25
  end
23
26
  end
24
27
 
25
28
  def vms_to_suspend
26
29
  if @options.all
27
- vms_to_suspend = VM.all_running
28
- else
29
- vm_name = @args.first
30
- unless Fission::VM.exists? vm_name
31
- Fission.ui.output ''
32
- Fission.ui.output_and_exit "Unable to find the VM #{vm_name} (#{Fission::VM.path(vm_name)})", 1
33
- end
30
+ response = VM.all_running
34
31
 
35
- unless VM.all_running.include?(vm_name)
36
- Fission.ui.output ''
37
- Fission.ui.output_and_exit "VM '#{vm_name}' is not running", 1
32
+ if response.successful?
33
+ vms = response.data
34
+ else
35
+ output_and_exit "There was an error getting the list of running VMs. The error was:\n#{response.message}", response.code
38
36
  end
39
-
40
- vms_to_suspend = [vm_name]
37
+ else
38
+ vms = [ VM.new(@args.first) ]
41
39
  end
40
+
41
+ vms
42
42
  end
43
43
 
44
44
  def option_parser
45
45
  optparse = OptionParser.new do |opts|
46
- opts.banner = "\nsuspend usage: fission suspend [vm | --all]"
46
+ opts.banner = "\nsuspend usage: fission suspend [vm_name | --all]"
47
47
 
48
48
  opts.on '--all', 'Suspend all running VMs' do
49
49
  @options.all = true
@@ -0,0 +1,21 @@
1
+ module Fission
2
+ module CommandHelpers
3
+
4
+ # Internal: Outputs the help text for a command and exits.
5
+ #
6
+ # command_name - The name of the command to use in the output text.
7
+ #
8
+ # Examples
9
+ #
10
+ # incorrect_arguments 'delete'
11
+ #
12
+ # Returns nothing.
13
+ # This will call the help class method for the help text. This will exit
14
+ # with the exit code 1.
15
+ def incorrect_arguments(command_name)
16
+ output "#{self.class.help}\n"
17
+ output_and_exit "Incorrect arguments for #{command_name} command", 1
18
+ end
19
+
20
+ end
21
+ end
@@ -1,9 +1,21 @@
1
1
  module Fission
2
2
  class Config
3
+
4
+ # Public: Gets/Sets the Hash of attributes.
3
5
  attr_accessor :attributes
4
6
 
7
+ # Public: Path to the Fission conf file (default: ~/.fissionrc).
5
8
  CONF_FILE = File.expand_path '~/.fissionrc'
6
9
 
10
+ # Public: Initializes a Config object. This also sets the default config
11
+ # attributes for 'vmrun_bin', 'vmrun_cmd', 'vm_dir', 'plist_file', and
12
+ # 'gui_bin'.
13
+ #
14
+ # Examples
15
+ #
16
+ # Fission::Config.new
17
+ #
18
+ # Returns a new Config instance.
7
19
  def initialize
8
20
  @attributes = {}
9
21
  load_from_file
@@ -12,13 +24,36 @@ module Fission
12
24
  @attributes['vm_dir'] = File.expand_path('~/Documents/Virtual Machines.localized/')
13
25
  end
14
26
 
27
+ @attributes['lease_file'] = '/var/db/vmware/vmnet-dhcpd-vmnet8.leases'
15
28
  @attributes['vmrun_bin'] = '/Library/Application Support/VMware Fusion/vmrun'
16
29
  @attributes['vmrun_cmd'] = "#{@attributes['vmrun_bin'].gsub(' ', '\ ')} -T fusion"
17
30
  @attributes['plist_file'] = File.expand_path('~/Library/Preferences/com.vmware.fusion.plist')
18
31
  @attributes['gui_bin'] = File.expand_path('/Applications/VMware Fusion.app/Contents/MacOS/vmware')
19
32
  end
20
33
 
34
+ # Public: Helper method to access config atributes. This is a shortcut for
35
+ # querying the config attributes.
36
+ #
37
+ # item - The config item to query.
38
+ #
39
+ # Examples
40
+ #
41
+ # Fission.config['vmrun_bin']
42
+ # # => '/foo/bar/vmrun'
43
+ #
44
+ # Returns the value of the specified config item.
45
+ def [](item)
46
+ @attributes[item]
47
+ end
48
+
21
49
  private
50
+ # Internal: Loads config values from the Fission conf file into attributes.
51
+ #
52
+ # Examples
53
+ #
54
+ # load_from_file
55
+ #
56
+ # Returns nothing.
22
57
  def load_from_file
23
58
  if File.file?(CONF_FILE)
24
59
  @attributes.merge!(YAML.load_file(CONF_FILE))
@@ -1,12 +1,20 @@
1
1
  module Fission
2
2
  class Fusion
3
3
 
4
- def self.is_running?
4
+ # Public: Determines if the VMware Fusion GUI application is running.
5
+ #
6
+ # Examples
7
+ #
8
+ # Fission::Fusion.running?
9
+ # # => true
10
+ #
11
+ # Returns a Boolean.
12
+ def self.running?
5
13
  command = "ps -ef | grep -v grep | grep -c "
6
- command << "#{Fission.config.attributes['gui_bin'].gsub(' ', '\ ')} 2>&1"
14
+ command << "#{Fission.config['gui_bin'].gsub(' ', '\ ')} 2>&1"
7
15
  output = `#{command}`
8
16
 
9
- output.strip.to_i > 0 ? true : false
17
+ output.strip.to_i > 0
10
18
  end
11
19
 
12
20
  end
@@ -0,0 +1,148 @@
1
+ module Fission
2
+ class Lease
3
+
4
+ # Public: Get/set the IP address for the lease.
5
+ attr_accessor :ip_address
6
+
7
+ # Public: Get/set the MAC address for the lease.
8
+ attr_accessor :mac_address
9
+
10
+ # Public: Get/set the start DateTime for the lease.
11
+ attr_accessor :start
12
+
13
+ # Public: Get/set the end DateTime for the lease.
14
+ attr_accessor :end
15
+
16
+ # Public: Initialize a Lease object.
17
+ #
18
+ # args - Hash of arguments:
19
+ # :ip_address - String which denotes the IP address of the lease.
20
+ # :mac_address - String which denotes the MAC address of the lease.
21
+ # :start - DateTime which denotes the start of the lease.
22
+ # :end - DateTime which denotes the end of the lease.
23
+ #
24
+ # Examples
25
+ #
26
+ # Returns a new Lease instance.
27
+ def initialize(args={})
28
+ @ip_address = args[:ip_address]
29
+ @mac_address = args[:mac_address]
30
+ @start = args[:start]
31
+ @end = args[:end]
32
+ end
33
+
34
+ # Public: Determine if the lease has expired or not.
35
+ #
36
+ # Examples:
37
+ #
38
+ # @lease.expired?
39
+ # # => true
40
+ #
41
+ # Returns a Boolean. The Boolean is determined by comparing the end
42
+ # attribute to the current date/time.
43
+ def expired?
44
+ @end < DateTime.now
45
+ end
46
+
47
+ # Public: Provides all of the known leases.
48
+ #
49
+ # Examples:
50
+ #
51
+ # Fission::Lease.all
52
+ # # => [#<Fission::Lease:0x000001008b6d88...>,
53
+ # #<Fission::Lease:0x000001008522c0...>]
54
+ #
55
+ # Returns a Response with the result.
56
+ # If successful, the Response's data attribute will be an Array of Lease
57
+ # objects. If no leases are found, then the Response's data attribute will
58
+ # be an empty Array.
59
+ # If there is an error, an unsuccessful Response will be returned.
60
+ def self.all
61
+ response = Response.new
62
+
63
+ if File.file? Fission.config['lease_file']
64
+ content = File.read Fission.config['lease_file']
65
+
66
+ response.data = content.split('}').collect do |entry|
67
+ parse entry
68
+ end
69
+
70
+ content = nil
71
+
72
+ response.code = 0
73
+ else
74
+ response.code = 1
75
+ response.message = "Unable to find the lease file '#{Fission.config['lease_file']}'"
76
+ end
77
+
78
+ response
79
+ end
80
+
81
+ # Public: Get lease information for a specific MAC address.
82
+ #
83
+ # mac_address - MAC address (String) to search for.
84
+ #
85
+ # Examples
86
+ #
87
+ # Fission::Lease.find_by_mac '00:11:AA:bb:cc:22'
88
+ # # => #<Fission::Lease:0x000001008522c0...>
89
+ #
90
+ # Returns a Response with the result.
91
+ # If successful, the Response's data attribute will be a Lease object if the
92
+ # MAC address was found. The Response's data attribute will be nil if the
93
+ # MAC address was not found.
94
+ # If there is an error, an unsuccessful Response will be returned.
95
+ def self.find_by_mac_address(mac_address)
96
+ all_response = all
97
+
98
+ if all_response.successful?
99
+ response = Response.new :code => 0
100
+ response.data = all_response.data.find { |l| l.mac_address == mac_address }
101
+ else
102
+ response = all_response
103
+ end
104
+
105
+ response
106
+ end
107
+
108
+ private
109
+ # Internal: Parses information out of a DHCP lease entry.
110
+ #
111
+ # entry - String of lease entry text.
112
+ #
113
+ # Examples
114
+ #
115
+ # Lease.parse my_lease_entry
116
+ # # => #<Fission::Lease:0x000001008522c0...>
117
+ #
118
+ #
119
+ # Returns a Lease object which is populated with the attribute that were
120
+ # found.
121
+ def self.parse(lease_entry)
122
+ lease = Lease.new
123
+
124
+ lease_entry.gsub! ';', ''
125
+
126
+ lease_entry.split("\n").each do |line|
127
+ next if line =~ /^#/
128
+
129
+ line.strip!
130
+
131
+ case line.strip
132
+ when /^lease/
133
+ lease.ip_address = line.split(' ')[1]
134
+ when /^starts/
135
+ lease.start = DateTime.parse(line.split(' ')[2..3].join(' '))
136
+ when /^end/
137
+ lease.end = DateTime.parse(line.split(' ')[2..3].join(' '))
138
+ when /^hardware/
139
+ lease.mac_address = line.split(' ')[2]
140
+ end
141
+ end
142
+
143
+ lease
144
+
145
+ end
146
+
147
+ end
148
+ end