tenderloin 0.2.0

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 (73) hide show
  1. data/LICENCE +21 -0
  2. data/README.md +50 -0
  3. data/Version +1 -0
  4. data/bin/loin +5 -0
  5. data/config/default.rb +26 -0
  6. data/lib/tenderloin/actions/base.rb +93 -0
  7. data/lib/tenderloin/actions/box/add.rb +22 -0
  8. data/lib/tenderloin/actions/box/destroy.rb +14 -0
  9. data/lib/tenderloin/actions/box/download.rb +63 -0
  10. data/lib/tenderloin/actions/box/unpackage.rb +46 -0
  11. data/lib/tenderloin/actions/runner.rb +138 -0
  12. data/lib/tenderloin/actions/vm/boot.rb +52 -0
  13. data/lib/tenderloin/actions/vm/destroy.rb +18 -0
  14. data/lib/tenderloin/actions/vm/halt.rb +14 -0
  15. data/lib/tenderloin/actions/vm/import.rb +32 -0
  16. data/lib/tenderloin/actions/vm/move_hard_drive.rb +53 -0
  17. data/lib/tenderloin/actions/vm/provision.rb +71 -0
  18. data/lib/tenderloin/actions/vm/reload.rb +17 -0
  19. data/lib/tenderloin/actions/vm/shared_folders.rb +47 -0
  20. data/lib/tenderloin/actions/vm/start.rb +17 -0
  21. data/lib/tenderloin/actions/vm/up.rb +55 -0
  22. data/lib/tenderloin/box.rb +143 -0
  23. data/lib/tenderloin/busy.rb +73 -0
  24. data/lib/tenderloin/cli.rb +59 -0
  25. data/lib/tenderloin/commands.rb +154 -0
  26. data/lib/tenderloin/config.rb +144 -0
  27. data/lib/tenderloin/downloaders/base.rb +13 -0
  28. data/lib/tenderloin/downloaders/file.rb +21 -0
  29. data/lib/tenderloin/downloaders/http.rb +47 -0
  30. data/lib/tenderloin/env.rb +156 -0
  31. data/lib/tenderloin/fusion_vm.rb +85 -0
  32. data/lib/tenderloin/ssh.rb +49 -0
  33. data/lib/tenderloin/util.rb +51 -0
  34. data/lib/tenderloin/vm.rb +63 -0
  35. data/lib/tenderloin/vmx_file.rb +28 -0
  36. data/lib/tenderloin.rb +14 -0
  37. data/script/tenderloin-ssh-expect.sh +23 -0
  38. data/templates/Tenderfile +8 -0
  39. data/test/tenderloin/actions/base_test.rb +32 -0
  40. data/test/tenderloin/actions/box/add_test.rb +37 -0
  41. data/test/tenderloin/actions/box/destroy_test.rb +18 -0
  42. data/test/tenderloin/actions/box/download_test.rb +118 -0
  43. data/test/tenderloin/actions/box/unpackage_test.rb +100 -0
  44. data/test/tenderloin/actions/runner_test.rb +262 -0
  45. data/test/tenderloin/actions/vm/boot_test.rb +55 -0
  46. data/test/tenderloin/actions/vm/destroy_test.rb +24 -0
  47. data/test/tenderloin/actions/vm/down_test.rb +32 -0
  48. data/test/tenderloin/actions/vm/export_test.rb +88 -0
  49. data/test/tenderloin/actions/vm/forward_ports_test.rb +50 -0
  50. data/test/tenderloin/actions/vm/halt_test.rb +27 -0
  51. data/test/tenderloin/actions/vm/import_test.rb +36 -0
  52. data/test/tenderloin/actions/vm/move_hard_drive_test.rb +108 -0
  53. data/test/tenderloin/actions/vm/package_test.rb +181 -0
  54. data/test/tenderloin/actions/vm/provision_test.rb +103 -0
  55. data/test/tenderloin/actions/vm/reload_test.rb +44 -0
  56. data/test/tenderloin/actions/vm/resume_test.rb +27 -0
  57. data/test/tenderloin/actions/vm/shared_folders_test.rb +117 -0
  58. data/test/tenderloin/actions/vm/start_test.rb +28 -0
  59. data/test/tenderloin/actions/vm/suspend_test.rb +27 -0
  60. data/test/tenderloin/actions/vm/up_test.rb +98 -0
  61. data/test/tenderloin/box_test.rb +139 -0
  62. data/test/tenderloin/busy_test.rb +83 -0
  63. data/test/tenderloin/commands_test.rb +269 -0
  64. data/test/tenderloin/config_test.rb +123 -0
  65. data/test/tenderloin/downloaders/base_test.rb +20 -0
  66. data/test/tenderloin/downloaders/file_test.rb +32 -0
  67. data/test/tenderloin/downloaders/http_test.rb +40 -0
  68. data/test/tenderloin/env_test.rb +345 -0
  69. data/test/tenderloin/ssh_test.rb +103 -0
  70. data/test/tenderloin/util_test.rb +64 -0
  71. data/test/tenderloin/vm_test.rb +89 -0
  72. data/test/test_helper.rb +92 -0
  73. metadata +241 -0
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Mitchell Hashimoto and John Bender
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Tenderloin
2
+
3
+ Tenderloin is a tool for building and distributing virtualized development environments.
4
+
5
+ It is based on [Vagrant](http://vagrantup.com), specifically the 0.1.4 release. This was
6
+ the simplest, and provided a good starting point
7
+
8
+ It is designed to use VMWare Fusion as the underlying provider. You will need Fusion 5.
9
+
10
+ ## Quick Start
11
+
12
+ gem install tenderloin
13
+
14
+ To build your first virtual environment:
15
+
16
+ loin init
17
+ loin box add base http://s3.lstoll.net/<todo>.box
18
+ loin up
19
+
20
+ The file describing your VM is called 'Tenderfile', but you can optionally change this with
21
+ the -f flag, to allow multiple VM descriptions in the same place.
22
+
23
+ ## Builsding base boxes
24
+
25
+ Currently base boxes are built manually. The process:
26
+
27
+ * Create image in Fusion
28
+ * Set user and password to 'tenderloin'
29
+ * Set sudo to not prompt for password
30
+ * Install VMWare additions
31
+ * Ensure you have a single .vmdk disk. If not, convert with:
32
+
33
+ /Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -r Virtual\ Disk.vmdk -t 0 precise64.vmdk
34
+
35
+ and then edit the vmx to point to this.
36
+
37
+ * Compress and shrink the disk
38
+
39
+ /Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -d precise64.vmdk
40
+ /Applications/VMware\ Fusion.app/Contents/Library/vmware-vdiskmanager -k precise64.vmdk
41
+
42
+ * Create a Tenderfile. Example:
43
+
44
+ Tenderloin::Config.run do |config|
45
+ config.vm.box_vmx = "precise64.vmx"
46
+ end
47
+
48
+ * Tar them up as a .box
49
+
50
+ tar -cvf precise64.box precise64.vmx Tenderfile precise64.vmdk
data/Version ADDED
@@ -0,0 +1 @@
1
+ 0.1.4
data/bin/loin ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'tenderloin'
4
+
5
+ Tenderloin::CLI.start(ARGV)
data/config/default.rb ADDED
@@ -0,0 +1,26 @@
1
+ Tenderloin::Config.run do |config|
2
+ # default config goes here
3
+ config.tenderloin.log_output = STDOUT
4
+ config.tenderloin.dotfile_name = ".tenderloin"
5
+ config.tenderloin.home = "~/.tenderloin"
6
+
7
+ config.ssh.username = "tenderloin"
8
+ config.ssh.password = "tenderloin"
9
+ # config.ssh.host = "localhost"
10
+ config.ssh.max_tries = 10
11
+ config.ssh.timeout = 30
12
+
13
+ config.vm.box_vmx = "box.vmx"
14
+ config.vm.project_directory = "/tenderloin"
15
+
16
+ config.package.name = 'tenderloin'
17
+ config.package.extension = '.box'
18
+
19
+ config.chef.enabled = false
20
+ config.chef.cookbooks_path = "cookbooks"
21
+ config.chef.provisioning_path = "/tmp/tenderloin-chef"
22
+ config.chef.json = {
23
+ :instance_role => "tenderloin",
24
+ :recipes => ["tenderloin_main"]
25
+ }
26
+ end
@@ -0,0 +1,93 @@
1
+ module Tenderloin
2
+ module Actions
3
+ # Base class for any command actions.
4
+ #
5
+ # Actions are the smallest unit of functionality found within
6
+ # Tenderloin. Tenderloin composes many actions together to execute
7
+ # its complex tasks while keeping the individual pieces of a
8
+ # task as discrete reusable actions. Actions are ran exclusively
9
+ # by an {Runner action runner} which is simply a subclass of {Runner}.
10
+ #
11
+ # Actions work by implementing any or all of the following methods
12
+ # which a {Runner} executes:
13
+ #
14
+ # * `prepare` - Called once for each action before any action has `execute!`
15
+ # called. This is meant for basic setup.
16
+ # * `execute!` - This is where the meat of the action typically goes;
17
+ # the main code which executes the action.
18
+ # * `cleanup` - This is called exactly once for each action after every
19
+ # other action is completed. It is meant for cleaning up any resources.
20
+ # * `rescue` - This is called if an exception occurs in _any action_. This
21
+ # gives every other action a chance to clean itself up.
22
+ #
23
+ # For details of each step of an action, read the specific function call
24
+ # documentation below.
25
+ class Base
26
+ # The {Runner runner} which is executing the action
27
+ attr_reader :runner
28
+
29
+ # Included so subclasses don't need to include it themselves.
30
+ include Tenderloin::Util
31
+
32
+ # Initialization of the action, passing any arguments which may have
33
+ # been given to the {Runner runner}. This method can be used by subclasses
34
+ # to save any of the configuration options which are passed in.
35
+ def initialize(runner, *args)
36
+ @runner = runner
37
+ end
38
+
39
+ # This method is called once per action, allowing the action
40
+ # to setup any callbacks, add more events, etc. Prepare is
41
+ # called in the order the actions are defined, and the action
42
+ # itself has no control over this.
43
+ #
44
+ # Examples of its usage:
45
+ #
46
+ # Perhaps we need an additional action only if a configuration is set:
47
+ #
48
+ # def prepare
49
+ # @vm.actions << FooAction if Tenderloin.config[:foo] == :bar
50
+ # end
51
+ #
52
+ def prepare; end
53
+
54
+ # This method is called once, after preparing, to execute the
55
+ # actual task. This method is responsible for calling any
56
+ # callbacks. Adding new actions here will have unpredictable
57
+ # effects and should never be done.
58
+ #
59
+ # Examples of its usage:
60
+ #
61
+ # def execute!
62
+ # @vm.invoke_callback(:before_oven, "cookies")
63
+ # # Do lots of stuff here
64
+ # @vm.invoke_callback(:after_oven, "more", "than", "one", "option")
65
+ # end
66
+ #
67
+ def execute!; end
68
+
69
+ # This method is called after all actions have finished executing.
70
+ # It is meant as a place where final cleanup code can be done, knowing
71
+ # that all other actions are finished using your data.
72
+ def cleanup; end
73
+
74
+ # This method is only called if some exception occurs in the chain
75
+ # of actions. If an exception is raised in any action in the current
76
+ # chain, then every action part of that chain has {#rescue} called
77
+ # before raising the exception further. This method should be used to
78
+ # perform any cleanup necessary in the face of errors.
79
+ #
80
+ # **Warning:** Since this method is called when an exception is already
81
+ # raised, be _extra careful_ when implementing this method to handle
82
+ # all your own exceptions, otherwise it'll mask the initially raised
83
+ # exception.
84
+ def rescue(exception); end
85
+ end
86
+
87
+ # An exception which occured within an action. This should be used instead of
88
+ # {Tenderloin::Util#error_and_exit error_and_exit}, since it allows the {Runner} to call
89
+ # {Base#rescue rescue} on all the actions and properly exit. Any message
90
+ # passed into the {ActionException} is then shown and and tenderloin exits.
91
+ class ActionException < Exception; end
92
+ end
93
+ end
@@ -0,0 +1,22 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module Box
4
+ # A meta-action which adds a box by downloading and unpackaging it.
5
+ # This action downloads and unpackages a box with a given URI. This
6
+ # is a _meta action_, meaning it simply adds more actions to the
7
+ # action chain, and those actions do the work.
8
+ #
9
+ # This is the action called by {Box#add}.
10
+ class Add < Base
11
+ def prepare
12
+ if File.exists?(@runner.directory)
13
+ raise ActionException.new("A box with the name '#{@runner.name}' already exists, please use another name or use `tenderloin box remove #{@runner.name}`")
14
+ end
15
+
16
+ @runner.add_action(Download)
17
+ @runner.add_action(Unpackage)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module Box
4
+ # Action to destroy a box. This action is not reversible and expects
5
+ # to be called by a {Box} object.
6
+ class Destroy < Base
7
+ def execute!
8
+ logger.info "Deleting box directory..."
9
+ FileUtils.rm_rf(@runner.directory)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,63 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module Box
4
+ # An action which acts on a box by downloading the box file from
5
+ # the given URI into a temporary location. This action parses a
6
+ # given URI and handles downloading it via one of the many Tenderloin
7
+ # downloads (such as {Tenderloin::Downloaders::File}).
8
+ #
9
+ # This action cleans itself up by removing the downloaded box file.
10
+ class Download < Base
11
+ BASENAME = "box"
12
+ BUFFERSIZE = 1048576 # 1 MB
13
+
14
+ attr_reader :downloader
15
+
16
+ def prepare
17
+ # Parse the URI given and prepare a downloader
18
+ uri = URI.parse(@runner.uri)
19
+ uri_map = [[URI::HTTP, Downloaders::HTTP], [URI::Generic, Downloaders::File]]
20
+
21
+ uri_map.find do |uri_type, downloader_klass|
22
+ if uri.is_a?(uri_type)
23
+ logger.info "#{uri_type} for URI, downloading via #{downloader_klass}..."
24
+ @downloader = downloader_klass.new
25
+ end
26
+ end
27
+
28
+ raise ActionException.new("Unknown URI type for box download.") unless @downloader
29
+ end
30
+
31
+ def execute!
32
+ with_tempfile do |tempfile|
33
+ download_to(tempfile)
34
+ @runner.temp_path = tempfile.path
35
+ end
36
+ end
37
+
38
+ def cleanup
39
+ if @runner.temp_path && File.exist?(@runner.temp_path)
40
+ logger.info "Cleaning up downloaded box..."
41
+ File.unlink(@runner.temp_path)
42
+ end
43
+ end
44
+
45
+ def rescue(exception)
46
+ cleanup
47
+ end
48
+
49
+ def with_tempfile
50
+ logger.info "Creating tempfile for storing box file..."
51
+ Tempfile.open(BASENAME, Env.tmp_path) do |tempfile|
52
+ yield tempfile
53
+ end
54
+ end
55
+
56
+ def download_to(f)
57
+ logger.info "Copying box to temporary location..."
58
+ downloader.download!(@runner.uri, f)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,46 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module Box
4
+ # This action unpackages a downloaded box file into its final
5
+ # box destination within the tenderloin home folder.
6
+ class Unpackage < Base
7
+
8
+ def execute!
9
+ @runner.invoke_around_callback(:unpackage) do
10
+ setup_box_dir
11
+ decompress
12
+ end
13
+ end
14
+
15
+ def rescue(exception)
16
+ if File.directory?(box_dir)
17
+ logger.info "An error occurred, rolling back box unpackaging..."
18
+ FileUtils.rm_rf(box_dir)
19
+ end
20
+ end
21
+
22
+ def setup_box_dir
23
+ if File.directory?(box_dir)
24
+ error_and_exit(<<-msg)
25
+ This box appears to already exist! Please call `tenderloin box remove #{@runner.name}`
26
+ and then try to add it again.
27
+ msg
28
+ end
29
+
30
+ FileUtils.mkdir_p(box_dir)
31
+ end
32
+
33
+ def box_dir
34
+ @runner.directory
35
+ end
36
+
37
+ def decompress
38
+ Dir.chdir(box_dir) do
39
+ logger.info "Extracting box to #{box_dir}..."
40
+ Archive::Tar::Minitar.unpack(@runner.temp_path, box_dir)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,138 @@
1
+ module Tenderloin
2
+ module Actions
3
+ # Base class for any class which will act as a runner
4
+ # for actions. A runner handles queueing up and executing actions,
5
+ # and executing the methods of an action in the proper order. The
6
+ # action runner also handles invoking callbacks that actions may
7
+ # request.
8
+ #
9
+ # # Executing Actions
10
+ #
11
+ # Actions can be executed by adding them and executing them all
12
+ # at once:
13
+ #
14
+ # runner = Tenderloin::Actions::Runner.new
15
+ # runner.add_action(FooAction)
16
+ # runner.add_action(BarAction)
17
+ # runner.add_action(BazAction)
18
+ # runner.execute!
19
+ #
20
+ # Single actions have a shorthand to be executed:
21
+ #
22
+ # Tenderloin::Actions::Runner.execute!(FooAction)
23
+ #
24
+ # Arguments may be passed into added actions by adding them after
25
+ # the action class:
26
+ #
27
+ # runner.add_action(FooAction, "many", "arguments", "may", "follow")
28
+ #
29
+ class Runner
30
+ include Tenderloin::Util
31
+
32
+ class << self
33
+ # Executes a specific action, optionally passing in any arguments to that
34
+ # action. This method is shorthand to initializing a runner, adding a single
35
+ # action, and executing it.
36
+ def execute!(action_klass, *args)
37
+ runner = new
38
+ runner.add_action(action_klass, *args)
39
+ runner.execute!
40
+ end
41
+ end
42
+
43
+ # Returns an array of all the actions in queue. Because this
44
+ # will persist accross calls (calling {#actions} twice will yield
45
+ # exactly the same object), to clear or modify it, use the ruby
46
+ # array methods which act on `self`, such as `Array#clear`.
47
+ #
48
+ # @return [Array]
49
+ def actions
50
+ @actions ||= []
51
+ end
52
+
53
+ # Returns the first action instance which matches the given class.
54
+ #
55
+ # @param [Class] action_klass The action to search for in the queue
56
+ # @return [Object]
57
+ def find_action(action_klass)
58
+ actions.find { |a| a.is_a?(action_klass) }
59
+ end
60
+
61
+ # Add an action to the list of queued actions to execute. This method
62
+ # appends the given action class to the end of the queue. Any arguments
63
+ # given after the class are passed into the class constructor.
64
+ def add_action(action_klass, *args)
65
+ actions << action_klass.new(self, *args)
66
+ end
67
+
68
+ # Execute the actions in queue. This method can also optionally be used
69
+ # to execute a single action on an instance. The syntax for executing a
70
+ # single method on an instance is the same as the {execute!} class method.
71
+ def execute!(single_action=nil, *args)
72
+
73
+ if single_action
74
+ actions.clear
75
+ add_action(single_action, *args)
76
+ end
77
+
78
+ # Raising it here might be too late and hard debug where the actions are comming from (meta actions)
79
+ raise DuplicateActionException.new if action_klasses.uniq.size < action_klasses.size
80
+
81
+ # Call the prepare method on each once its
82
+ # initialized, then call the execute! method
83
+ begin
84
+ [:prepare, :execute!, :cleanup].each do |method|
85
+ actions.each do |action|
86
+ action.send(method)
87
+ end
88
+ end
89
+ rescue Exception => e
90
+ # Run the rescue code to do any emergency cleanup
91
+ actions.each do |action|
92
+ action.rescue(e)
93
+ end
94
+
95
+ # If its an ActionException, error and exit the message
96
+ if e.is_a?(ActionException)
97
+ error_and_exit(e.message)
98
+ return
99
+ end
100
+
101
+ # Finally, reraise the exception
102
+ raise
103
+ end
104
+
105
+ # Clear the actions
106
+ actions.clear
107
+ end
108
+
109
+ # Invokes an "around callback" which invokes before_name and
110
+ # after_name for the given callback name, yielding a block between
111
+ # callback invokations.
112
+ def invoke_around_callback(name, *args)
113
+ invoke_callback("before_#{name}".to_sym, *args)
114
+ yield
115
+ invoke_callback("after_#{name}".to_sym, *args)
116
+ end
117
+
118
+ # Invokes a single callback. This method will go through each action
119
+ # and call the method given in the parameter `name` if the action
120
+ # responds to it.
121
+ def invoke_callback(name, *args)
122
+ # Attempt to call the method for the callback on each of the
123
+ # actions
124
+ results = []
125
+ actions.each do |action|
126
+ results << action.send(name, *args) if action.respond_to?(name)
127
+ end
128
+ results
129
+ end
130
+
131
+ def action_klasses
132
+ actions.map { |a| a.class }
133
+ end
134
+ end
135
+
136
+ class DuplicateActionException < Exception; end
137
+ end
138
+ end
@@ -0,0 +1,52 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module VM
4
+ class Boot < Base
5
+ def execute!
6
+ @runner.invoke_around_callback(:boot) do
7
+ # Startup the VM
8
+ boot
9
+
10
+ # Wait for it to complete booting, or error if we could
11
+ # never detect it booted up successfully
12
+ if !wait_for_boot
13
+ error_and_exit(<<-error)
14
+ Failed to connect to VM! Failed to boot?
15
+ error
16
+ end
17
+ end
18
+ end
19
+
20
+ def collect_shared_folders
21
+ # The root shared folder for the project
22
+ ["tenderloin-root", Env.root_path, Tenderloin.config.vm.project_directory]
23
+ end
24
+
25
+ def boot
26
+ logger.info "Booting VM..."
27
+
28
+ @runner.fusion_vm.start(:headless => true)
29
+ end
30
+
31
+ def wait_for_boot(sleeptime=5)
32
+ logger.info "Waiting for VM to boot..."
33
+
34
+ Tenderloin.config[:ssh][:max_tries].to_i.times do |i|
35
+ logger.info "Trying to connect (attempt ##{i+1} of #{Tenderloin.config[:ssh][:max_tries]})..."
36
+ ip = @runner.fusion_vm.ip
37
+
38
+ if ip && Tenderloin::SSH.up?(ip)
39
+ logger.info "VM booted and ready for use on IP: " + ip
40
+ return true
41
+ end
42
+
43
+ sleep sleeptime
44
+ end
45
+
46
+ logger.info "Failed to connect to VM! Failed to boot?"
47
+ false
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ require 'fileutils'
2
+
3
+ module Tenderloin
4
+ module Actions
5
+ module VM
6
+ class Destroy < Base
7
+ def execute!
8
+ @runner.execute!(Halt) if @runner.running?
9
+ @runner.invoke_around_callback(:destroy) do
10
+ logger.info "Destroying VM and associated drives..."
11
+ @runner.fusion_vm.delete
12
+ FileUtils.rm_rf(File.dirname(@runner.vmx_path))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module VM
4
+ class Halt < Base
5
+ def execute!
6
+ raise ActionException.new("VM is not running! Nothing to shut down!") unless @runner.running?
7
+
8
+ logger.info "Forcing shutdown of VM..."
9
+ @runner.fusion_vm.stop(:force => true)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'fileutils'
2
+
3
+ module Tenderloin
4
+ module Actions
5
+ module VM
6
+ class Import < Base
7
+ def execute!
8
+ @runner.invoke_around_callback(:import) do
9
+ Busy.busy do
10
+ logger.info "Importing base VM (#{Tenderloin::Env.box.vmx_file})..."
11
+ # Use the first argument passed to the action
12
+ # @runner.vm = VirtualBox::VM.import(Tenderloin::Env.box.ovf_file)
13
+ @runner.vm_id = (0...15).map{ ('a'..'z').to_a[rand(26)] }.join
14
+ vmdir = File.join(Tenderloin::Env.vms_path, @runner.vm_id)
15
+
16
+ FileUtils.mkdir_p(vmdir) unless Dir.exists?(vmdir)
17
+
18
+ # Copy the VMX over
19
+ FileUtils.cp(Tenderloin::Env.box.vmx_file, File.join(vmdir, @runner.vm_id + ".vmx"))
20
+
21
+ # Copy all VMDK's over
22
+ Dir.glob(File.join(File.dirname(Tenderloin::Env.box.vmx_file), "*.vmdk")) do |f|
23
+ FileUtils.cp File.expand_path(f), vmdir
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ module Tenderloin
2
+ module Actions
3
+ module VM
4
+ class MoveHardDrive < Base
5
+ def execute!
6
+ unless @runner.powered_off?
7
+ error_and_exit(<<-error)
8
+ The virtual machine must be powered off to move its disk.
9
+ error
10
+ return
11
+ end
12
+
13
+ destroy_drive_after { clone_and_attach }
14
+ end
15
+
16
+ def hard_drive
17
+ @hard_drive ||= find_hard_drive
18
+ end
19
+
20
+ # TODO won't work if the first disk is not the boot disk or even if there are multiple disks
21
+ def find_hard_drive
22
+ @runner.vm.storage_controllers.each do |sc|
23
+ sc.devices.each do |d|
24
+ return d if d.image.is_a?(VirtualBox::HardDrive)
25
+ end
26
+ end
27
+ end
28
+
29
+ def clone_and_attach
30
+ logger.info "Cloning current VM Disk to new location (#{new_image_path})..."
31
+ hard_drive.image = hard_drive.image.clone(new_image_path, Tenderloin.config.vm.disk_image_format, true)
32
+
33
+ logger.info "Attaching new disk to VM ..."
34
+ @runner.vm.save
35
+ end
36
+
37
+ def destroy_drive_after
38
+ old_image = hard_drive.image
39
+
40
+ yield
41
+
42
+ logger.info "Destroying old VM Disk (#{old_image.filename})..."
43
+ old_image.destroy(true)
44
+ end
45
+
46
+ # Returns the path to the new location for the hard drive
47
+ def new_image_path
48
+ File.join(Tenderloin.config.vm.hd_location, hard_drive.image.filename)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end