fission 0.4.0 → 0.5.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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