fission 0.4.0 → 0.5.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 (79) hide show
  1. data/.ruby-gemset +1 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG.md +12 -3
  5. data/README.md +45 -19
  6. data/bin/fission +1 -1
  7. data/fission.gemspec +3 -2
  8. data/lib/fission.rb +15 -0
  9. data/lib/fission/action/shell_executor.rb +37 -0
  10. data/lib/fission/action/snapshot/creator.rb +81 -0
  11. data/lib/fission/action/snapshot/deleter.rb +85 -0
  12. data/lib/fission/action/snapshot/lister.rb +90 -0
  13. data/lib/fission/action/snapshot/reverter.rb +81 -0
  14. data/lib/fission/action/vm/cloner.rb +191 -0
  15. data/lib/fission/action/vm/deleter.rb +73 -0
  16. data/lib/fission/action/vm/lister.rb +138 -0
  17. data/lib/fission/action/vm/starter.rb +88 -0
  18. data/lib/fission/action/vm/stopper.rb +79 -0
  19. data/lib/fission/action/vm/suspender.rb +68 -0
  20. data/lib/fission/cli.rb +21 -117
  21. data/lib/fission/command.rb +39 -0
  22. data/lib/fission/command/clone.rb +11 -6
  23. data/lib/fission/command/delete.rb +11 -6
  24. data/lib/fission/command/info.rb +62 -0
  25. data/lib/fission/command/snapshot_create.rb +9 -3
  26. data/lib/fission/command/snapshot_delete.rb +38 -0
  27. data/lib/fission/command/snapshot_list.rb +9 -3
  28. data/lib/fission/command/snapshot_revert.rb +9 -3
  29. data/lib/fission/command/start.rb +11 -6
  30. data/lib/fission/command/status.rb +9 -1
  31. data/lib/fission/command/stop.rb +9 -3
  32. data/lib/fission/command/suspend.rb +11 -6
  33. data/lib/fission/command_helpers.rb +18 -4
  34. data/lib/fission/command_line_parser.rb +189 -0
  35. data/lib/fission/config.rb +1 -1
  36. data/lib/fission/fusion.rb +3 -4
  37. data/lib/fission/lease.rb +2 -1
  38. data/lib/fission/metadata.rb +6 -1
  39. data/lib/fission/response.rb +17 -9
  40. data/lib/fission/version.rb +1 -1
  41. data/lib/fission/vm.rb +142 -382
  42. data/lib/fission/vm_configuration.rb +79 -0
  43. data/spec/fission/action/execute_shell_command_spec.rb +25 -0
  44. data/spec/fission/action/snapshot/creator_spec.rb +77 -0
  45. data/spec/fission/action/snapshot/deleter_spec.rb +84 -0
  46. data/spec/fission/action/snapshot/lister_spec.rb +67 -0
  47. data/spec/fission/action/snapshot/reverter_spec.rb +76 -0
  48. data/spec/fission/action/vm/cloner_spec.rb +198 -0
  49. data/spec/fission/action/vm/deleter_spec.rb +79 -0
  50. data/spec/fission/action/vm/lister_spec.rb +164 -0
  51. data/spec/fission/action/vm/starter_spec.rb +88 -0
  52. data/spec/fission/action/vm/stopper_spec.rb +71 -0
  53. data/spec/fission/action/vm/suspender_spec.rb +59 -0
  54. data/spec/fission/cli_spec.rb +32 -157
  55. data/spec/fission/command/clone_spec.rb +9 -3
  56. data/spec/fission/command/delete_spec.rb +11 -3
  57. data/spec/fission/command/info_spec.rb +130 -0
  58. data/spec/fission/command/snapshot_create_spec.rb +11 -3
  59. data/spec/fission/command/snapshot_delete_spec.rb +74 -0
  60. data/spec/fission/command/snapshot_list_spec.rb +11 -3
  61. data/spec/fission/command/snapshot_revert_spec.rb +11 -3
  62. data/spec/fission/command/start_spec.rb +11 -3
  63. data/spec/fission/command/status_spec.rb +16 -5
  64. data/spec/fission/command/stop_spec.rb +11 -3
  65. data/spec/fission/command/suspend_spec.rb +11 -3
  66. data/spec/fission/command_helpers_spec.rb +27 -5
  67. data/spec/fission/command_line_parser_spec.rb +267 -0
  68. data/spec/fission/command_spec.rb +16 -1
  69. data/spec/fission/config_spec.rb +3 -3
  70. data/spec/fission/fusion_spec.rb +11 -6
  71. data/spec/fission/lease_spec.rb +81 -45
  72. data/spec/fission/metadata_spec.rb +29 -6
  73. data/spec/fission/response_spec.rb +20 -9
  74. data/spec/fission/ui_spec.rb +1 -1
  75. data/spec/fission/vm_configuration_spec.rb +132 -0
  76. data/spec/fission/vm_spec.rb +393 -750
  77. data/spec/helpers/command_helpers.rb +1 -1
  78. metadata +93 -15
  79. data/.rvmrc +0 -1
@@ -0,0 +1,189 @@
1
+ module Fission
2
+
3
+ class CommandLineParser
4
+
5
+ # Internal: Creates a new Fission::CommandLineParser object.
6
+ #
7
+ # args - The command line arguments to parse. This is expected to be in the
8
+ # same format of ARGV (Array) (default: ARGV).
9
+ #
10
+ # Examples:
11
+ #
12
+ # CommandLineParser.new ['foo', 'bar']
13
+ #
14
+ # CommandLineParser.new
15
+ #
16
+ # Returns a Fission::CommandLineParser object.
17
+ def initialize(args=ARGV)
18
+ @args = args
19
+
20
+ gather_commands_and_summaries
21
+
22
+ setup_options_parser
23
+ end
24
+
25
+ # Internal: Parses the command line arguments. If the arguments are
26
+ # invalid, the appropriate help will be output and then will exit. If the
27
+ # arguments are valid, then the @command variable will be set to a new
28
+ # instance of the determined command class.
29
+ #
30
+ # Examples:
31
+ #
32
+ # @command_line_parser.parse
33
+ #
34
+ # Returns nothing.
35
+ def parse
36
+ @options_parser.order! @args
37
+
38
+ determine_command_provided
39
+ self
40
+ rescue OptionParser::InvalidOption => e
41
+ ui.output e
42
+ show_all_help
43
+ exit 1
44
+ end
45
+
46
+ # Internal: Accessor for an instance of the determined command. This is set
47
+ # by running the parse method.
48
+ #
49
+ # Examples:
50
+ #
51
+ # @command_line_parser.command
52
+ #
53
+ # Returns an instance of the determined command if the arguments are valid.
54
+ # Returns nil if the parse command has not yet been run.
55
+ def command
56
+ @command
57
+ end
58
+
59
+ private
60
+ # Internal: Sets up the base option parser.
61
+ #
62
+ # Examples:
63
+ #
64
+ # @cli.setup_option_parser
65
+ #
66
+ # Returns nothing. This will set the @option_parser instance variable.
67
+ def setup_options_parser
68
+ @options_parser = OptionParser.new do |opts|
69
+ opts.banner = "\nUsage: fission [options] COMMAND [arguments]"
70
+
71
+ opts.on_head('-v', '--version', 'Output the version of fission') do
72
+ ui.output VERSION
73
+ exit 0
74
+ end
75
+
76
+ opts.on_head('-h', '--help', 'Displays this message') do
77
+ show_all_help
78
+ exit 0
79
+ end
80
+ end
81
+ end
82
+
83
+ # Internal: Provides the help of all of the known commands.
84
+ #
85
+ # Examples
86
+ #
87
+ # @cli.commands_help
88
+ #
89
+ # Outputs the summary text for all known commands.
90
+ def commands_help
91
+ longest_cmd = @commands.inject do |longest, cmd_name|
92
+ longest.length > cmd_name.length ? longest : cmd_name
93
+ end
94
+
95
+ ui.output "\nCommands:"
96
+
97
+ Hash[@command_names_and_summaries.sort].each_pair do |name, summary|
98
+ ui.output_printf "%-#{longest_cmd.length}s %s\n", name, summary
99
+ end
100
+ end
101
+
102
+ # Internal: Determines all of the available commands and their summaries.
103
+ #
104
+ # Examples
105
+ #
106
+ # @cli.command_names_and_summaries
107
+ # # => { 'clone' => 'Clones a VM', 'stop' => 'Stops a VM' }
108
+ #
109
+ # Returns nothing. This will set the @command_names_and_summaries instance
110
+ # variable with the result.
111
+ def gather_commands_and_summaries
112
+ @command_names_and_summaries = Command.descendants.inject({}) do |result, klass|
113
+ cmd = klass.new
114
+ result[cmd.command_name] = cmd.summary
115
+ result
116
+ end
117
+
118
+ @commands = @command_names_and_summaries.keys.sort
119
+ end
120
+
121
+ # Internal: Determines the command that has been provided. If it is
122
+ # determined that an invalid command is provided, then the help/usage will
123
+ # be displayed and it will exit.
124
+ #
125
+ # Examples:
126
+ #
127
+ # @cli.determine_command_to_execute
128
+ #
129
+ # Returns nothing. This will set the @command instance variable to an instance
130
+ # of the appropriate command class (assuming it is valid).
131
+ def determine_command_provided
132
+ if @commands.include? @args.first
133
+ @command = Command.const_get(@args.first.capitalize).new @args.drop 1
134
+ elsif is_snapshot_command?
135
+ klass = @args.take(2).map {|c| c.capitalize}.join('')
136
+ @command = Command.const_get(klass).new @args.drop 2
137
+ else
138
+ show_all_help
139
+ exit 1
140
+ end
141
+ end
142
+
143
+ # Internal: Determines if the provided command is a snapshot related
144
+ # command. This will use the @args instance variable to make the
145
+ # determination.
146
+ #
147
+ # Examples
148
+ #
149
+ # @cli.is_snapshot_command? ['foo', 'bar']
150
+ # # => false
151
+ #
152
+ # @cli.is_snapshot_command? ['snapshot', 'list']
153
+ # # => true
154
+ #
155
+ # Returns a Boolean of whether a snapshot command was given or not.
156
+ def is_snapshot_command?
157
+ @args.first == 'snapshot' &&
158
+ @args.count > 1 &&
159
+ @commands.include?(@args.take(2).join(' '))
160
+ end
161
+
162
+ # Internal: Outputs the usage as well as the known commands and their
163
+ # summaries.
164
+ #
165
+ # Examples
166
+ #
167
+ # @cli.show_all_help
168
+ # # => 'fission options command arguments ....'
169
+ #
170
+ # Returns nothing.
171
+ def show_all_help
172
+ ui.output @options_parser
173
+ commands_help
174
+ end
175
+
176
+ # Internal: Helper method for outputting text to the ui
177
+ #
178
+ # Examples
179
+ #
180
+ # @cli.ui.output 'foo'
181
+ #
182
+ # Returns a UI instance.
183
+ def ui
184
+ @ui ||= UI.new
185
+ end
186
+
187
+ end
188
+
189
+ end
@@ -27,7 +27,7 @@ module Fission
27
27
 
28
28
  load_from_file
29
29
 
30
- @attributes['vmrun_cmd'] = "#{@attributes['vmrun_bin'].gsub(' ', '\ ')} -T fusion"
30
+ @attributes['vmrun_cmd'] = "'#{@attributes['vmrun_bin']}' -T fusion"
31
31
  end
32
32
 
33
33
  # Public: Helper method to access config atributes. This is a shortcut for
@@ -11,10 +11,9 @@ module Fission
11
11
  # Returns a Boolean.
12
12
  def self.running?
13
13
  command = "ps -ef | grep -v grep | grep -c "
14
- command << "#{Fission.config['gui_bin'].gsub(' ', '\ ')} 2>&1"
15
- output = `#{command}`
16
-
17
- output.strip.to_i > 0
14
+ command << "'#{Fission.config['gui_bin']}' 2>&1"
15
+ command_executor = Fission::Action::ShellExecutor.new command
16
+ command_executor.execute['output'].strip.to_i > 0
18
17
  end
19
18
 
20
19
  end
@@ -97,7 +97,8 @@ module Fission
97
97
 
98
98
  if all_response.successful?
99
99
  response = Response.new :code => 0
100
- response.data = all_response.data.find { |l| l.mac_address == mac_address }
100
+ leases = all_response.data.find_all { |l| l.mac_address == mac_address }
101
+ response.data = leases.sort_by { |l| l.end }.last
101
102
  else
102
103
  response = all_response
103
104
  end
@@ -85,7 +85,12 @@ module Fission
85
85
  #
86
86
  # Returns nothing.
87
87
  def delete_vm_favorite_entry(vm_path)
88
- @content['VMFavoritesListDefaults2'].delete_if { |vm| vm['path'] == vm_path }
88
+ if @content.has_key?('VMFavoritesListDefaults2')
89
+ @content['VMFavoritesListDefaults2'].delete_if { |vm| vm['path'] == vm_path }
90
+ end
91
+ if @content.has_key?('fusionInitialSessions')
92
+ @content['fusionInitialSessions'].delete_if {|vm| vm['documentPath'] == vm_path}
93
+ end
89
94
  end
90
95
 
91
96
  end
@@ -57,18 +57,26 @@ module Fission
57
57
  @code == 0
58
58
  end
59
59
 
60
- # Public: Helper method to create a new Response object when running a
61
- # command line tool.
60
+ # Internal: Helper method to create a new Response object when using
61
+ # executing a command through Fission::Action::ShellExecutor.
62
62
  #
63
- # cmd_output - This should be the output of the command.
63
+ # shell_execute_result - This should be the result of running 'execute' on
64
+ # a ShellExecutor object.
65
+ #
66
+ # Examples:
67
+ #
68
+ # Response.from_shell_executor shell_execute_result
64
69
  #
65
70
  # Returns a Response.
66
- # The Response's code attribute will be set to the value of '$?'. The
67
- # Response's message attribute will be set to the provided command output
68
- # if, and only if, the Response is unsuccessful.
69
- def self.from_command(cmd_output)
70
- response = new :code => $?.exitstatus
71
- response.message = cmd_output unless response.successful?
71
+ # The code attribute of the Response will be set to the exit_status
72
+ # attribute of the provided process_status data. The message attribute of
73
+ # the Response will be set to the output of the provided data if, and only
74
+ # if, the Response is unsuccessful.
75
+ def self.from_shell_executor(shell_execute_result)
76
+ response = new :code => shell_execute_result['process_status'].exitstatus
77
+ unless response.successful?
78
+ response.message = shell_execute_result['output']
79
+ end
72
80
  response
73
81
  end
74
82
 
@@ -1,3 +1,3 @@
1
1
  module Fission
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0.beta.1"
3
3
  end
@@ -21,33 +21,23 @@ module Fission
21
21
  # If successful, the Response's data attribute will be nil.
22
22
  # If there is an error, an unsuccessful Response will be returned.
23
23
  def create_snapshot(name)
24
- unless exists?
25
- return Response.new :code => 1, :message => 'VM does not exist'
26
- end
27
-
28
- running_response = running?
29
- return running_response unless running_response.successful?
30
-
31
- unless running_response.data
32
- message = 'The VM must be running in order to take a snapshot.'
33
- return Response.new :code => 1, :message => message
34
- end
35
-
36
- conf_file_response = conf_file
37
- return conf_file_response unless conf_file_response.successful?
38
-
39
- snapshots_response = snapshots
40
- return snapshots_response unless snapshots_response.successful?
41
-
42
- if snapshots_response.data.include? name
43
- message = "There is already a snapshot named '#{name}'."
44
- return Response.new :code => 1, :message => message
45
- end
46
-
47
- command = "#{vmrun_cmd} snapshot "
48
- command << "#{conf_file_response.data} \"#{name}\" 2>&1"
24
+ Fission::Action::Snapshot::Creator.new(self).create_snapshot(name)
25
+ end
49
26
 
50
- Response.from_command(`#{command}`)
27
+ # Public: Deletes a snapshot for a VM. The snapshot to delete must exist.
28
+ # If the Fusion GUI is running, then the VM must also be running.
29
+ #
30
+ # name - The name of the snapshot to delete.
31
+ #
32
+ # Examples
33
+ #
34
+ # @vm.delete_snapshot('foo_snap_1')
35
+ #
36
+ # Returns a Response with the result.
37
+ # If successful, the Response's data attribute will be nil.
38
+ # If there is an error, an unsuccessful Response will be returned.
39
+ def delete_snapshot(name)
40
+ Fission::Action::Snapshot::Deleter.new(self).delete_snapshot(name)
51
41
  end
52
42
 
53
43
  # Public: Reverts the VM to the specified snapshot. The snapshot to revert
@@ -63,32 +53,7 @@ module Fission
63
53
  # If successful, the Response's data attribute will be nil.
64
54
  # If there is an error, an unsuccessful Response will be returned.
65
55
  def revert_to_snapshot(name)
66
- unless exists?
67
- return Response.new :code => 1, :message => 'VM does not exist'
68
- end
69
-
70
- if Fusion.running?
71
- message = 'It looks like the Fusion GUI is currently running. '
72
- message << 'A VM cannot be reverted to a snapshot when the Fusion GUI is running. '
73
- message << 'Exit the Fusion GUI and try again.'
74
- return Response.new :code => 1, :message => message
75
- end
76
-
77
- conf_file_response = conf_file
78
- return conf_file_response unless conf_file_response.successful?
79
-
80
- snapshots_response = snapshots
81
- return snapshots_response unless snapshots_response.successful?
82
-
83
- unless snapshots_response.data.include? name
84
- message = "Unable to find a snapshot named '#{name}'."
85
- return Response.new :code => 1, :message => message
86
- end
87
-
88
- command = "#{vmrun_cmd} revertToSnapshot "
89
- command << "#{conf_file_response.data} \"#{name}\" 2>&1"
90
-
91
- Response.from_command(`#{command}`)
56
+ Fission::Action::Snapshot::Reverter.new(self).revert_to_snapshot(name)
92
57
  end
93
58
 
94
59
  # Public: List the snapshots for a VM.
@@ -103,27 +68,28 @@ module Fission
103
68
  # snapshot names (String).
104
69
  # If there is an error, an unsuccessful Response will be returned.
105
70
  def snapshots
106
- unless exists?
107
- return Response.new :code => 1, :message => 'VM does not exist'
108
- end
109
-
110
- conf_file_response = conf_file
111
- return conf_file_response unless conf_file_response.successful?
112
-
113
- command = "#{vmrun_cmd} listSnapshots "
114
- command << "#{conf_file_response.data} 2>&1"
115
- output = `#{command}`
116
-
117
- response = Response.new :code => $?.exitstatus
118
-
119
- if response.successful?
120
- snaps = output.split("\n").select { |s| !s.include? 'Total snapshots:' }
121
- response.data = snaps.map { |s| s.strip }
122
- else
123
- response.message = output
124
- end
71
+ Fission::Action::Snapshot::Lister.new(self).snapshots
72
+ end
125
73
 
126
- response
74
+ # Public: Deletes a VM. The VM must not be running in order to delete it.
75
+ # As there are a number issues with the Fusion command line tool for
76
+ # deleting VMs, this is a best effort. The VM must not be running when this
77
+ # method is called. This essentially deletes the VM directory and attempts
78
+ # to remove the relevant entries from the Fusion plist file. It's highly
79
+ # recommended to delete VMs without the Fusion GUI running. If the Fusion
80
+ # GUI is running this method should succeed, but it's been observed that
81
+ # Fusion will recreate the plist data which is deleted. This leads to
82
+ # 'missing' VMs in the Fusion GUI.
83
+ #
84
+ # Examples
85
+ #
86
+ # @vm.delete
87
+ #
88
+ # Returns a Response with the result.
89
+ # If successful, the Response's data attribute will be nil.
90
+ # If there is an error, an unsuccessful Response will be returned.
91
+ def delete
92
+ Fission::Action::VM::Deleter.new(self).delete
127
93
  end
128
94
 
129
95
  # Public: Starts a VM. The VM must not be running in order to start it.
@@ -144,36 +110,7 @@ module Fission
144
110
  # If successful, the Response's data attribute will be nil.
145
111
  # If there is an error, an unsuccessful Response will be returned.
146
112
  def start(options={})
147
- unless exists?
148
- return Response.new :code => 1, :message => 'VM does not exist'
149
- end
150
-
151
- running_response = running?
152
- return running_response unless running_response.successful?
153
-
154
- if running_response.data
155
- return Response.new :code => 1, :message => 'VM is already running'
156
- end
157
-
158
- conf_file_response = conf_file
159
- return conf_file_response unless conf_file_response.successful?
160
-
161
- unless options[:headless].blank?
162
- if Fusion.running?
163
- message = 'It looks like the Fusion GUI is currently running. '
164
- message << 'A VM cannot be started in headless mode when the Fusion GUI is running. '
165
- message << 'Exit the Fusion GUI and try again.'
166
- return Response.new :code => 1, :message => message
167
- end
168
- end
169
-
170
- command = "#{vmrun_cmd} start "
171
- command << "#{conf_file_response.data} "
172
-
173
- command << (options[:headless].blank? ? 'gui ' : 'nogui ')
174
- command << '2>&1'
175
-
176
- Response.from_command(`#{command}`)
113
+ Fission::Action::VM::Starter.new(self).start(options)
177
114
  end
178
115
 
179
116
  # Public: Stops a VM. The VM must be running in order to stop it.
@@ -195,26 +132,7 @@ module Fission
195
132
  # If successful, the Response's data attribute will be nil.
196
133
  # If there is an error, an unsuccessful Response will be returned.
197
134
  def stop(options={})
198
- unless exists?
199
- return Response.new :code => 1, :message => 'VM does not exist'
200
- end
201
-
202
- running_response = running?
203
- return running_response unless running_response.successful?
204
-
205
- unless running_response.data
206
- return Response.new :code => 1, :message => 'VM is not running'
207
- end
208
-
209
- conf_file_response = conf_file
210
- return conf_file_response unless conf_file_response.successful?
211
-
212
- command = "#{vmrun_cmd} stop "
213
- command << "#{conf_file_response.data} "
214
- command << 'hard ' unless options[:hard].blank?
215
- command << '2>&1'
216
-
217
- Response.from_command(`#{command}`)
135
+ Fission::Action::VM::Stopper.new(self).stop(options)
218
136
  end
219
137
 
220
138
  # Public: Suspends a VM. The VM must be running in order to suspend it.
@@ -227,24 +145,35 @@ module Fission
227
145
  # If successful, the Response's data attribute will be nil.
228
146
  # If there is an error, an unsuccessful Response will be returned.
229
147
  def suspend
230
- unless exists?
231
- return Response.new :code => 1, :message => 'VM does not exist'
232
- end
148
+ Fission::Action::VM::Suspender.new(self).suspend
149
+ end
233
150
 
234
- running_response = running?
235
- return running_response unless running_response.successful?
151
+ # Public: Provides virtual hardware info the VM
152
+ #
153
+ # Examples:
154
+ #
155
+ # @vm.hardware_info.data
156
+ # # => {'cpus' => 2, 'memory' => 1024}
157
+ #
158
+ # Returns a Response with the result.
159
+ # If successful, the Response's data attribute will be a Hash with the
160
+ # info found.
161
+ # If there is an error, an unsuccessful Response will be returned.
162
+ def hardware_info
163
+ config_response = conf_file_data
164
+ return config_response unless config_response.successful?
236
165
 
237
- unless running_response.data
238
- return Response.new :code => 1, :message => 'VM is not running'
239
- end
166
+ response = Response.new :code => 0, :data => {}
240
167
 
241
- conf_file_response = conf_file
242
- return conf_file_response unless conf_file_response.successful?
168
+ response.data['cpus'] = 1
243
169
 
244
- command = "#{vmrun_cmd} suspend "
245
- command << "#{conf_file_response.data} 2>&1"
170
+ { 'cpus' => 'numvcpus', 'memory' => 'memsize' }.each_pair do |k,v|
171
+ unless config_response.data[v].blank?
172
+ response.data[k] = config_response.data[v].to_i
173
+ end
174
+ end
246
175
 
247
- Response.from_command(`#{command}`)
176
+ response
248
177
  end
249
178
 
250
179
  # Public: Provides the MAC addresses for a VM.
@@ -297,30 +226,76 @@ module Fission
297
226
  # attribute will be an empty Hash.
298
227
  # If there is an error, an unsuccessful Response will be returned.
299
228
  def network_info
300
- conf_file_response = conf_file
301
- return conf_file_response unless conf_file_response.successful?
229
+ config_response = conf_file_data
230
+ return config_response unless config_response.successful?
302
231
 
303
232
  response = Response.new :code => 0, :data => {}
304
233
 
305
234
  interface_pattern = /^ethernet\d+/
306
235
  mac_pattern = /(\w\w[:-]\w\w[:-]\w\w[:-]\w\w[:-]\w\w[:-]\w\w)/
307
236
 
308
- File.open conf_file_response.data, 'r' do |f|
309
- f.grep(mac_pattern).each do |line|
310
- int = line.scan(interface_pattern)[0]
311
- mac = line.scan(mac_pattern)[0].first
312
- response.data[int] = {}
313
- response.data[int]['mac_address'] = mac
314
-
237
+ config_response.data.each_pair do |k,v|
238
+ if v =~ mac_pattern
239
+ mac = v
240
+ int = k.scan(interface_pattern).first
241
+ response.data[int] = { 'mac_address' => mac }
315
242
  lease_response = Fission::Lease.find_by_mac_address mac
316
243
  return lease_response unless lease_response.successful?
317
244
 
318
245
  response.data[int]['ip_address'] = nil
319
-
320
246
  if lease_response.data
321
247
  response.data[int]['ip_address'] = lease_response.data.ip_address
322
248
  end
249
+ end
250
+ end
251
+
252
+ response
253
+ end
254
+
255
+ # Public: Provides the Fussion GuestOS profile.
256
+ #
257
+ # Examples
258
+ #
259
+ # @vm.guestos.data
260
+ # # => 'debian5'
261
+ #
262
+ # Returns a Response with the result.
263
+ # If the Response is successful, the Response's data attribute will
264
+ # be populated with a string that is the guest operatingsystem used for the
265
+ # virtual machine.
266
+ # If there is an error, an unsuccessful Response will be returned.
267
+ def guestos
268
+ config_response = conf_file_data
269
+ return config_response unless config_response.successful?
323
270
 
271
+ response = Response.new :code => 0, :data => {}
272
+ response.data = config_response.data.fetch 'guestOS', ''
273
+ response
274
+ end
275
+
276
+ # Public: Provides various uuids associated with a VM.
277
+ #
278
+ # Examples
279
+ #
280
+ # @vm.uuids.data
281
+ # # => {"bios"=>"56 4d ee 72 3b 7e 47 67-69 aa 65 cb 5e 40 3f 21",
282
+ # "location"=>"56 4d 2e 15 f4 ed 00 a7-c5 99 43 32 b8 76 ef d5"}
283
+ #
284
+ # Returns a Response with the result.
285
+ # If the Response is successful, the Response's data attribute will
286
+ # be populated with a hash that is comprised of the various uuids that are
287
+ # associated with each VM. If the VM is newly created they will be the same
288
+ # but if you selected 'I Moved It' from the Fusion GUI they will differ.
289
+ # If there is an error, an unsuccessful Response will be returned.
290
+ def uuids
291
+ config_response = conf_file_data
292
+ return config_response unless config_response.successful?
293
+
294
+ response = Response.new :code => 0, :data => {}
295
+
296
+ { 'bios' => 'uuid.bios', 'location' => 'uuid.location' }.each_pair do |k,v|
297
+ unless config_response.data[v].blank?
298
+ response.data[k] = config_response.data[v]
324
299
  end
325
300
  end
326
301
 
@@ -439,6 +414,22 @@ module Fission
439
414
  response
440
415
  end
441
416
 
417
+ # Public: Provides the configuration data stored in the VM's configuration
418
+ # file ('.vmx').
419
+ #
420
+ # Examples
421
+ #
422
+ # @vm.conf_file_data
423
+ #
424
+ # Returns a Response with the result.
425
+ # If successful, the Response's data attribute with be a Hash of the
426
+ # configuration data. All of the keys/values in the configuration data will
427
+ # be a String.
428
+ # If there is an error, an unsuccessful Response will be returned.
429
+ def conf_file_data
430
+ VMConfiguration.new(self).config_data
431
+ end
432
+
442
433
  # Public: Determines the path to the VM's config file ('.vmx').
443
434
  #
444
435
  # Examples
@@ -485,8 +476,6 @@ module Fission
485
476
  end
486
477
  end
487
478
 
488
- response.data.gsub! ' ', '\ ' if response.successful?
489
-
490
479
  response
491
480
  end
492
481
 
@@ -519,14 +508,7 @@ module Fission
519
508
  # empty Array.
520
509
  # If there is an error, an unsuccessful Response will be returned.
521
510
  def self.all
522
- vm_dirs = Dir[File.join Fission.config['vm_dir'], '*.vmwarevm'].select do |d|
523
- File.directory? d
524
- end
525
-
526
- response = Response.new :code => 0
527
- response.data = vm_dirs.collect { |d| new(File.basename d, '.vmwarevm') }
528
-
529
- response
511
+ Fission::Action::VM::Lister.new.all
530
512
  end
531
513
 
532
514
  # Public: Provides all of the VMs which are currently running.
@@ -543,25 +525,7 @@ module Fission
543
525
  # attribute will be an empty Array.
544
526
  # If there is an error, an unsuccessful Response will be returned.
545
527
  def self.all_running
546
- command = "#{Fission.config['vmrun_cmd']} list"
547
-
548
- output = `#{command}`
549
-
550
- response = Response.new :code => $?.exitstatus
551
-
552
- if response.successful?
553
- vms = output.split("\n").select do |vm|
554
- vm.include?('.vmx') && File.exists?(vm) && File.extname(vm) == '.vmx'
555
- end
556
-
557
- response.data = vms.collect do |vm|
558
- new File.basename(File.dirname(vm), '.vmwarevm')
559
- end
560
- else
561
- response.message = output
562
- end
563
-
564
- response
528
+ Fission::Action::VM::Lister.new.all_running
565
529
  end
566
530
 
567
531
  # Public: Provides a list of all of the VMs and their associated status
@@ -576,34 +540,7 @@ module Fission
576
540
  # names as keys and their status as the values.
577
541
  # If there is an error, an unsuccessful Repsonse will be returned.
578
542
  def self.all_with_status
579
- all_response = all
580
- return all_response unless all_response.successful?
581
-
582
- all_vms = all_response.data
583
-
584
- running_response = all_running
585
- return running_response unless running_response.successful?
586
-
587
- response = Response.new :code => 0
588
-
589
- all_running_vm_names = running_response.data.collect { |v| v.name }
590
-
591
- response.data = all_vms.inject({}) do |result, vm|
592
- if all_running_vm_names.include? vm.name
593
- status = 'running'
594
- else
595
- if vm.suspend_file_exists?
596
- status = 'suspended'
597
- else
598
- status = 'not running'
599
- end
600
- end
601
-
602
- result[vm.name] = status
603
- result
604
- end
605
-
606
- response
543
+ Fission::Action::VM::Lister.new.all_with_status
607
544
  end
608
545
 
609
546
  # Public: Creates a new VM which is a clone of an existing VM. As Fusion
@@ -622,189 +559,12 @@ module Fission
622
559
  # If successful, the Response's data attribute will be nil.
623
560
  # If there is an error, an unsuccessful Response will be returned.
624
561
  def self.clone(source_vm_name, target_vm_name)
625
- source_vm = new source_vm_name
626
- target_vm = new target_vm_name
627
-
628
- unless source_vm.exists?
629
- return Response.new :code => 1, :message => 'VM does not exist'
630
- end
631
-
632
- if target_vm.exists?
633
- return Response.new :code => 1, :message => 'VM already exists'
634
- end
635
-
636
- FileUtils.cp_r source_vm.path, target_vm.path
637
-
638
- rename_vm_files source_vm.name, target_vm.name
639
- update_config source_vm.name, target_vm.name
640
-
641
- Response.new :code => 0
642
- end
643
-
644
- # Public: Deletes a VM. The VM must not be running in order to delete it.
645
- # As there are a number issues with the Fusion command line tool for
646
- # deleting VMs, this is a best effort. The VM must not be running when this
647
- # method is called. This essentially deletes the VM directory and attempts
648
- # to remove the relevant entries from the Fusion plist file. It's highly
649
- # recommended to delete VMs without the Fusion GUI running. If the Fusion
650
- # GUI is running this method should succeed, but it's been observed that
651
- # Fusion will recreate the plist data which is deleted. This leads to
652
- # 'missing' VMs in the Fusion GUI.
653
- #
654
- # Examples
655
- #
656
- # @vm.delete
657
- #
658
- # Returns a Response with the result.
659
- # If successful, the Response's data attribute will be nil.
660
- # If there is an error, an unsuccessful Response will be returned.
661
- def delete
662
- unless exists?
663
- return Response.new :code => 1, :message => 'VM does not exist'
664
- end
665
-
666
- running_response = running?
667
- return running_response unless running_response.successful?
668
-
669
- if running_response.data
670
- message = 'The VM must not be running in order to delete it.'
671
- return Response.new :code => 1, :message => message
672
- end
673
-
674
- FileUtils.rm_rf path
675
- Metadata.delete_vm_info path
676
-
677
- Response.new :code => 0
562
+ @source_vm = new source_vm_name
563
+ @target_vm = new target_vm_name
564
+ Fission::Action::VM::Cloner.new(@source_vm, @target_vm).clone
678
565
  end
679
566
 
680
567
  private
681
- # Internal: Renames the files of a newly cloned VM.
682
- #
683
- # from - The VM name that was used as the source of the clone.
684
- # to - The name of the newly cloned VM.
685
- #
686
- # Examples
687
- #
688
- # Fission::VM.rename_vm_files 'foo', 'bar'
689
- #
690
- # Returns nothing.
691
- def self.rename_vm_files(from, to)
692
- to_vm = new to
693
-
694
- files_to_rename(from, to).each do |file|
695
- text_to_replace = File.basename(file, File.extname(file))
696
-
697
- if File.extname(file) == '.vmdk'
698
- if file.match /\-s\d+\.vmdk/
699
- text_to_replace = file.partition(/\-s\d+.vmdk/).first
700
- end
701
- end
702
-
703
- unless File.exists?(File.join(to_vm.path, file.gsub(text_to_replace, to)))
704
- FileUtils.mv File.join(to_vm.path, file),
705
- File.join(to_vm.path, file.gsub(text_to_replace, to))
706
- end
707
- end
708
- end
709
-
710
- # Internal: Provides the list of files which need to be renamed in a newly
711
- # cloned VM directory.
712
- #
713
- # from - The VM name that was used as the source of the clone.
714
- # to - The name of the newly cloned VM.
715
- #
716
- # Examples
717
- #
718
- # Fission::VM.files_to_rename 'foo', 'bar'
719
- # # => ['/vms/vm1/foo.vmdk', '/vms/vm1/foo.vmx', 'vms/vm1/blah.other']
720
- #
721
- # Returns an Array containing the paths (String) to the files to rename.
722
- # The paths which match the from name will preceed any other files found in
723
- # the newly cloned VM directory.
724
- def self.files_to_rename(from, to)
725
- to_vm = new to
726
-
727
- files_which_match_source_vm = []
728
- other_files = []
729
-
730
- Dir.entries(to_vm.path).each do |f|
731
- unless f == '.' || f == '..'
732
- f.include?(from) ? files_which_match_source_vm << f : other_files << f
733
- end
734
- end
735
-
736
- files_which_match_source_vm + other_files
737
- end
738
-
739
- # Internal: Provides the list of file extensions for VM related files.
740
- #
741
- # Examples
742
- #
743
- # Fission::VM.vm_file_extension
744
- # # => ['.nvram', '.vmdk', '.vmem']
745
- #
746
- # Returns an Array containing the file extensions of VM realted files.
747
- # The file extensions returned are Strings and include a '.'.
748
- def self.vm_file_extensions
749
- ['.nvram', '.vmdk', '.vmem', '.vmsd', '.vmss', '.vmx', '.vmxf']
750
- end
751
-
752
- # Internal: Updates config files for a newly cloned VM. This will update any
753
- # files with the extension of '.vmx', '.vmxf', and '.vmdk'. Any binary
754
- # '.vmdk' files will be skipped.
755
- #
756
- # from - The VM name that was used as the source of the clone.
757
- # to - The name of the newly cloned VM.
758
- #
759
- # Examples
760
- #
761
- # Fission::VM.update_config 'foo', 'bar'
762
- #
763
- # Returns nothing.
764
- def self.update_config(from, to)
765
- to_vm = new to
766
-
767
- ['.vmx', '.vmxf', '.vmdk'].each do |ext|
768
- file = File.join to_vm.path, "#{to}#{ext}"
769
-
770
- unless File.binary?(file)
771
- text = (File.read file).gsub from, to
772
- File.open(file, 'w'){ |f| f.print text }
773
- end
774
-
775
- clean_up_conf_file(file) if ext == '.vmx'
776
- end
777
- end
778
-
779
- # Internal: Cleans up the conf file (*.vmx) for a newly cloned VM. This
780
- # includes removing generated MAC addresses, setting up for a new UUID, and
781
- # disable VMware tools warning.
782
- #
783
- # conf_file_path - Aboslute path to the VM's conf file (.vmx).
784
- #
785
- # Examples
786
- #
787
- # VM.clean_up_conf_file '/vms/foo/foo.vmx'
788
- #
789
- # Returns nothing.
790
- def self.clean_up_conf_file(conf_file_path)
791
- conf_items_patterns = [ /^tools\.remindInstall.*\n/,
792
- /^uuid\.action.*\n/,
793
- /^ethernet\.+generatedAddress.*\n/ ]
794
-
795
- content = File.read conf_file_path
796
-
797
- conf_items_patterns.each do |pattern|
798
- content.gsub(pattern, '').strip
799
- end
800
-
801
- content << "\n"
802
- content << "tools.remindInstall = \"FALSE\"\n"
803
- content << "uuid.action = \"create\"\n"
804
-
805
- File.open(conf_file_path, 'w') { |f| f.print content }
806
- end
807
-
808
568
  # Internal: Helper for getting the configured vmrun_cmd value.
809
569
  #
810
570
  # Examples