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.
- data/LICENCE +21 -0
- data/README.md +50 -0
- data/Version +1 -0
- data/bin/loin +5 -0
- data/config/default.rb +26 -0
- data/lib/tenderloin/actions/base.rb +93 -0
- data/lib/tenderloin/actions/box/add.rb +22 -0
- data/lib/tenderloin/actions/box/destroy.rb +14 -0
- data/lib/tenderloin/actions/box/download.rb +63 -0
- data/lib/tenderloin/actions/box/unpackage.rb +46 -0
- data/lib/tenderloin/actions/runner.rb +138 -0
- data/lib/tenderloin/actions/vm/boot.rb +52 -0
- data/lib/tenderloin/actions/vm/destroy.rb +18 -0
- data/lib/tenderloin/actions/vm/halt.rb +14 -0
- data/lib/tenderloin/actions/vm/import.rb +32 -0
- data/lib/tenderloin/actions/vm/move_hard_drive.rb +53 -0
- data/lib/tenderloin/actions/vm/provision.rb +71 -0
- data/lib/tenderloin/actions/vm/reload.rb +17 -0
- data/lib/tenderloin/actions/vm/shared_folders.rb +47 -0
- data/lib/tenderloin/actions/vm/start.rb +17 -0
- data/lib/tenderloin/actions/vm/up.rb +55 -0
- data/lib/tenderloin/box.rb +143 -0
- data/lib/tenderloin/busy.rb +73 -0
- data/lib/tenderloin/cli.rb +59 -0
- data/lib/tenderloin/commands.rb +154 -0
- data/lib/tenderloin/config.rb +144 -0
- data/lib/tenderloin/downloaders/base.rb +13 -0
- data/lib/tenderloin/downloaders/file.rb +21 -0
- data/lib/tenderloin/downloaders/http.rb +47 -0
- data/lib/tenderloin/env.rb +156 -0
- data/lib/tenderloin/fusion_vm.rb +85 -0
- data/lib/tenderloin/ssh.rb +49 -0
- data/lib/tenderloin/util.rb +51 -0
- data/lib/tenderloin/vm.rb +63 -0
- data/lib/tenderloin/vmx_file.rb +28 -0
- data/lib/tenderloin.rb +14 -0
- data/script/tenderloin-ssh-expect.sh +23 -0
- data/templates/Tenderfile +8 -0
- data/test/tenderloin/actions/base_test.rb +32 -0
- data/test/tenderloin/actions/box/add_test.rb +37 -0
- data/test/tenderloin/actions/box/destroy_test.rb +18 -0
- data/test/tenderloin/actions/box/download_test.rb +118 -0
- data/test/tenderloin/actions/box/unpackage_test.rb +100 -0
- data/test/tenderloin/actions/runner_test.rb +262 -0
- data/test/tenderloin/actions/vm/boot_test.rb +55 -0
- data/test/tenderloin/actions/vm/destroy_test.rb +24 -0
- data/test/tenderloin/actions/vm/down_test.rb +32 -0
- data/test/tenderloin/actions/vm/export_test.rb +88 -0
- data/test/tenderloin/actions/vm/forward_ports_test.rb +50 -0
- data/test/tenderloin/actions/vm/halt_test.rb +27 -0
- data/test/tenderloin/actions/vm/import_test.rb +36 -0
- data/test/tenderloin/actions/vm/move_hard_drive_test.rb +108 -0
- data/test/tenderloin/actions/vm/package_test.rb +181 -0
- data/test/tenderloin/actions/vm/provision_test.rb +103 -0
- data/test/tenderloin/actions/vm/reload_test.rb +44 -0
- data/test/tenderloin/actions/vm/resume_test.rb +27 -0
- data/test/tenderloin/actions/vm/shared_folders_test.rb +117 -0
- data/test/tenderloin/actions/vm/start_test.rb +28 -0
- data/test/tenderloin/actions/vm/suspend_test.rb +27 -0
- data/test/tenderloin/actions/vm/up_test.rb +98 -0
- data/test/tenderloin/box_test.rb +139 -0
- data/test/tenderloin/busy_test.rb +83 -0
- data/test/tenderloin/commands_test.rb +269 -0
- data/test/tenderloin/config_test.rb +123 -0
- data/test/tenderloin/downloaders/base_test.rb +20 -0
- data/test/tenderloin/downloaders/file_test.rb +32 -0
- data/test/tenderloin/downloaders/http_test.rb +40 -0
- data/test/tenderloin/env_test.rb +345 -0
- data/test/tenderloin/ssh_test.rb +103 -0
- data/test/tenderloin/util_test.rb +64 -0
- data/test/tenderloin/vm_test.rb +89 -0
- data/test/test_helper.rb +92 -0
- metadata +241 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class Provision < Base
|
5
|
+
def execute!
|
6
|
+
chown_provisioning_folder
|
7
|
+
setup_json
|
8
|
+
setup_solo_config
|
9
|
+
run_chef_solo
|
10
|
+
end
|
11
|
+
|
12
|
+
def chown_provisioning_folder
|
13
|
+
logger.info "Setting permissions on provisioning folder..."
|
14
|
+
SSH.execute do |ssh|
|
15
|
+
ssh.exec!("sudo chown #{Tenderloin.config.ssh.username} #{Tenderloin.config.chef.provisioning_path}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_json
|
20
|
+
logger.info "Generating JSON and uploading..."
|
21
|
+
|
22
|
+
# Set up initial configuration
|
23
|
+
data = {
|
24
|
+
:config => Tenderloin.config,
|
25
|
+
:directory => Tenderloin.config.vm.project_directory,
|
26
|
+
}
|
27
|
+
|
28
|
+
# And wrap it under the "tenderloin" namespace
|
29
|
+
data = { :tenderloin => data }
|
30
|
+
|
31
|
+
# Merge with the "extra data" which isn't put under the
|
32
|
+
# tenderloin namespace by default
|
33
|
+
data.merge!(Tenderloin.config.chef.json)
|
34
|
+
|
35
|
+
json = data.to_json
|
36
|
+
|
37
|
+
SSH.upload!(StringIO.new(json), File.join(Tenderloin.config.chef.provisioning_path, "dna.json"))
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_solo_config
|
41
|
+
solo_file = <<-solo
|
42
|
+
file_cache_path "#{Tenderloin.config.chef.provisioning_path}"
|
43
|
+
cookbook_path "#{cookbooks_path}"
|
44
|
+
solo
|
45
|
+
|
46
|
+
logger.info "Uploading chef-solo configuration script..."
|
47
|
+
SSH.upload!(StringIO.new(solo_file), File.join(Tenderloin.config.chef.provisioning_path, "solo.rb"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_chef_solo
|
51
|
+
logger.info "Running chef recipes..."
|
52
|
+
SSH.execute do |ssh|
|
53
|
+
ssh.exec!("cd #{Tenderloin.config.chef.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json") do |channel, data, stream|
|
54
|
+
# TODO: Very verbose. It would be easier to save the data and only show it during
|
55
|
+
# an error, or when verbosity level is set high
|
56
|
+
logger.info("#{stream}: #{data}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def cookbooks_path
|
62
|
+
File.join(Tenderloin.config.chef.provisioning_path, "cookbooks")
|
63
|
+
end
|
64
|
+
|
65
|
+
def collect_shared_folders
|
66
|
+
["tenderloin-provisioning", File.expand_path(Tenderloin.config.chef.cookbooks_path, Env.root_path), cookbooks_path]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class Reload < Base
|
5
|
+
def prepare
|
6
|
+
steps = [SharedFolders, Boot]
|
7
|
+
steps.unshift(Halt) if @runner.vm.running?
|
8
|
+
steps << Provision if Tenderloin.config.chef.enabled
|
9
|
+
|
10
|
+
steps.each do |action_klass|
|
11
|
+
@runner.add_action(action_klass)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class SharedFolders < Base
|
5
|
+
def shared_folders
|
6
|
+
shared_folders = @runner.invoke_callback(:collect_shared_folders)
|
7
|
+
|
8
|
+
# Basic filtering of shared folders. Basically only verifies that
|
9
|
+
# the result is an array of 3 elements. In the future this should
|
10
|
+
# also verify that the host path exists, the name is valid,
|
11
|
+
# and that the guest path is valid.
|
12
|
+
shared_folders.collect do |folder|
|
13
|
+
if folder.is_a?(Array) && folder.length == 3
|
14
|
+
folder
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
def before_boot
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def after_boot
|
26
|
+
logger.info "Creating shared folders metadata..."
|
27
|
+
|
28
|
+
shared_folders.each do |name, hostpath, guestpath|
|
29
|
+
@runner.fusion_vm.share_folder(name, hostpath)
|
30
|
+
@runner.fusion_vm.enable_shared_folders
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.info "Linking shared folders..."
|
34
|
+
|
35
|
+
Tenderloin::SSH.execute(@runner.fusion_vm.ip) do |ssh|
|
36
|
+
shared_folders.each do |name, hostpath, guestpath|
|
37
|
+
logger.info "-- #{name}: #{guestpath}"
|
38
|
+
ssh.exec!("sudo ln -s /mnt/hgfs/#{name} #{guestpath}")
|
39
|
+
ssh.exec!("sudo chown #{Tenderloin.config.ssh.username} #{guestpath}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class Start < Base
|
5
|
+
def prepare
|
6
|
+
# Start is a "meta-action" so it really just queues up a bunch
|
7
|
+
# of other actions in its place:
|
8
|
+
steps = [SharedFolders, Boot]
|
9
|
+
|
10
|
+
steps.each do |action_klass|
|
11
|
+
@runner.add_action(action_klass)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Actions
|
3
|
+
module VM
|
4
|
+
class Up < Base
|
5
|
+
def prepare
|
6
|
+
# If the dotfile is not a file, raise error
|
7
|
+
if File.exist?(Env.dotfile_path) && !File.file?(Env.dotfile_path)
|
8
|
+
raise ActionException.new(<<-msg)
|
9
|
+
The dotfile which Tenderloin uses to store the UUID of the project's
|
10
|
+
virtual machine already exists and is not a file! The dotfile is
|
11
|
+
currently configured to be `#{Env.dotfile_path}`
|
12
|
+
|
13
|
+
To change this value, please see `config.tenderloin.dotfile_name`
|
14
|
+
msg
|
15
|
+
end
|
16
|
+
|
17
|
+
# Up is a "meta-action" so it really just queues up a bunch
|
18
|
+
# of other actions in its place:
|
19
|
+
steps = [Import, SharedFolders, Boot]
|
20
|
+
steps << Provision if Tenderloin.config.chef.enabled
|
21
|
+
steps.insert(0, MoveHardDrive) if Tenderloin.config.vm.hd_location
|
22
|
+
|
23
|
+
steps.each do |action_klass|
|
24
|
+
@runner.add_action(action_klass)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def after_import
|
29
|
+
persist
|
30
|
+
setup_uuid_mac
|
31
|
+
end
|
32
|
+
|
33
|
+
def persist
|
34
|
+
logger.info "Persisting the VM UUID (#{@runner.vm_id})..."
|
35
|
+
Env.persist_vm(@runner.vm_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_uuid_mac
|
39
|
+
logger.info "Resetting VMX UUID, MAC and Display Name..."
|
40
|
+
|
41
|
+
VMXFile.with_vmx_data(@runner.vmx_path) do |data|
|
42
|
+
data.delete "ethernet0.addressType"
|
43
|
+
data.delete "uuid.location"
|
44
|
+
data.delete "uuid.bios"
|
45
|
+
data.delete "ethernet0.generatedAddress"
|
46
|
+
data.delete "ethernet1.generatedAddress"
|
47
|
+
data.delete "ethernet0.generatedAddressOffset"
|
48
|
+
data.delete "ethernet1.generatedAddressOffset"
|
49
|
+
data['displayName'] = "tenderloin-" + @runner.vm_id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
# Represents a "box," which is simply a packaged tenderloin environment.
|
3
|
+
# Boxes are simply `tar` files which contain an exported VirtualBox
|
4
|
+
# virtual machine, at the least. They are created with `tenderloin package`
|
5
|
+
# and may contain additional files if specified by the creator. This
|
6
|
+
# class serves to help manage these boxes, although most of the logic
|
7
|
+
# is kicked out to actions.
|
8
|
+
#
|
9
|
+
# What can the {Box} class do?
|
10
|
+
#
|
11
|
+
# * Find boxes
|
12
|
+
# * Add existing boxes (from some URI)
|
13
|
+
# * Delete existing boxes
|
14
|
+
#
|
15
|
+
# # Finding Boxes
|
16
|
+
#
|
17
|
+
# Using the {Box.find} method, you can search for existing boxes. This
|
18
|
+
# method will return `nil` if none is found or an instance of {Box}
|
19
|
+
# otherwise.
|
20
|
+
#
|
21
|
+
# box = Tenderloin::Box.find("base")
|
22
|
+
# if box.nil?
|
23
|
+
# puts "Box not found!"
|
24
|
+
# else
|
25
|
+
# puts "Box exists at #{box.directory}"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# # Adding a Box
|
29
|
+
#
|
30
|
+
# Boxes can be added from any URI. Some schemas aren't supported; if this
|
31
|
+
# is the case, the error will output to the logger.
|
32
|
+
#
|
33
|
+
# Tenderloin::Box.add("foo", "http://myfiles.com/foo.box")
|
34
|
+
#
|
35
|
+
# # Destroying a box
|
36
|
+
#
|
37
|
+
# Boxes can be deleted as well. This method is _final_ and there is no way
|
38
|
+
# to undo this action once it is completed.
|
39
|
+
#
|
40
|
+
# box = Tenderloin::Box.find("foo")
|
41
|
+
# box.destroy
|
42
|
+
#
|
43
|
+
class Box < Actions::Runner
|
44
|
+
# The name of the box.
|
45
|
+
attr_accessor :name
|
46
|
+
|
47
|
+
# The URI for a new box. This is not available for existing boxes.
|
48
|
+
attr_accessor :uri
|
49
|
+
|
50
|
+
# The temporary path to the downloaded or copied box. This should
|
51
|
+
# only be used internally.
|
52
|
+
attr_accessor :temp_path
|
53
|
+
|
54
|
+
class <<self
|
55
|
+
# Returns an array of all created boxes, as strings.
|
56
|
+
#
|
57
|
+
# @return [Array<String>]
|
58
|
+
def all
|
59
|
+
results = []
|
60
|
+
|
61
|
+
Dir.open(Env.boxes_path) do |dir|
|
62
|
+
dir.each do |d|
|
63
|
+
next if d == "." || d == ".." || !File.directory?(File.join(Env.boxes_path, d))
|
64
|
+
results << d.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
results
|
69
|
+
end
|
70
|
+
|
71
|
+
# Finds a box with the given name. This method searches for a box
|
72
|
+
# with the given name, returning `nil` if none is found or returning
|
73
|
+
# a {Box} instance otherwise.
|
74
|
+
#
|
75
|
+
# @param [String] name The name of the box
|
76
|
+
# @return [Box] Instance of {Box} representing the box found
|
77
|
+
def find(name)
|
78
|
+
return nil unless File.directory?(directory(name))
|
79
|
+
new(name)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds a new box with given name from the given URI. This method
|
83
|
+
# begins the process of adding a box from a given URI by setting up
|
84
|
+
# the {Box} instance and calling {#add}.
|
85
|
+
#
|
86
|
+
# @param [String] name The name of the box
|
87
|
+
# @param [String] uri URI to the box file
|
88
|
+
def add(name, uri)
|
89
|
+
box = new
|
90
|
+
box.name = name
|
91
|
+
box.uri = uri
|
92
|
+
box.add
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the directory to a box of the given name. The name given
|
96
|
+
# as a parameter is not checked for existence; this method simply
|
97
|
+
# returns the directory which would be used if the box did exist.
|
98
|
+
#
|
99
|
+
# @param [String] name Name of the box whose directory you're interested in.
|
100
|
+
# @return [String] Full path to the box directory.
|
101
|
+
def directory(name)
|
102
|
+
File.join(Env.boxes_path, name)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Creates a new box instance. Given an optional `name` parameter,
|
107
|
+
# newly created instance will have that name, otherwise it defaults
|
108
|
+
# to `nil`.
|
109
|
+
#
|
110
|
+
# **Note:** This method does not actually _create_ the box, but merely
|
111
|
+
# returns a new, abstract representation of it. To add a box, see {#add}.
|
112
|
+
def initialize(name=nil)
|
113
|
+
@name = name
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns path to the vmx file of the box. This contains the config
|
117
|
+
#
|
118
|
+
# @return [String]
|
119
|
+
def vmx_file
|
120
|
+
File.join(directory, Tenderloin.config.vm.box_vmx)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Begins the process of adding a box to the tenderloin installation. This
|
124
|
+
# method requires that `name` and `uri` be set. The logic of this method
|
125
|
+
# is kicked out to the {Actions::Box::Add add box} action.
|
126
|
+
def add
|
127
|
+
execute!(Actions::Box::Add)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Beings the process of destroying this box.
|
131
|
+
def destroy
|
132
|
+
execute!(Actions::Box::Destroy)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the directory to the location of this boxes content in the local
|
136
|
+
# filesystem.
|
137
|
+
#
|
138
|
+
# @return [String]
|
139
|
+
def directory
|
140
|
+
self.class.directory(self.name)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
def self.busy?
|
3
|
+
Busy.busy?
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.busy(&block)
|
7
|
+
Busy.busy(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
class Busy
|
11
|
+
extend Tenderloin::Util
|
12
|
+
|
13
|
+
@@busy = false
|
14
|
+
@@mutex = Mutex.new
|
15
|
+
@@trap_thread = nil
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def busy?
|
19
|
+
@@busy
|
20
|
+
end
|
21
|
+
|
22
|
+
def busy=(val)
|
23
|
+
@@busy = val
|
24
|
+
end
|
25
|
+
|
26
|
+
def busy(&block)
|
27
|
+
@@mutex.synchronize do
|
28
|
+
begin
|
29
|
+
Signal.trap("INT") { wait_for_not_busy }
|
30
|
+
Busy.busy = true
|
31
|
+
runner = Thread.new(block) { block.call }
|
32
|
+
runner.join
|
33
|
+
ensure
|
34
|
+
# In the case an exception is thrown, make sure we restore
|
35
|
+
# busy back to some sane state.
|
36
|
+
Busy.busy = false
|
37
|
+
|
38
|
+
# Make sure that the trap thread completes, if it is running
|
39
|
+
trap_thread.join if trap_thread
|
40
|
+
|
41
|
+
# And restore the INT trap to the default
|
42
|
+
Signal.trap("INT", "DEFAULT")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def wait_for_not_busy(sleeptime=5)
|
48
|
+
@@trap_thread ||= Thread.new do
|
49
|
+
# Wait while the app is busy
|
50
|
+
loop do
|
51
|
+
break unless busy?
|
52
|
+
logger.info "Waiting for tenderloin to clean itself up..."
|
53
|
+
sleep sleeptime
|
54
|
+
end
|
55
|
+
|
56
|
+
# Exit out of the entire script
|
57
|
+
logger.info "Exiting tenderloin..."
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Used for testing
|
63
|
+
def reset_trap_thread!
|
64
|
+
@@trap_thread = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the trap thread
|
68
|
+
def trap_thread
|
69
|
+
@@trap_thread
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'tenderloin'
|
3
|
+
|
4
|
+
module Tenderloin
|
5
|
+
|
6
|
+
class CLI < Thor
|
7
|
+
class_option :file, :aliases => :'-f', :default => "Tenderfile"
|
8
|
+
|
9
|
+
no_tasks do
|
10
|
+
def setup
|
11
|
+
$ROOTFILE_NAME = options[:file].dup.freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "up [--file <tenderfile>]", "Boots the VM"
|
16
|
+
def up()
|
17
|
+
setup
|
18
|
+
Tenderloin::Commands.up
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "halt [--file <tenderfile>]", "Force shuts down the running VM"
|
22
|
+
def halt()
|
23
|
+
setup
|
24
|
+
Tenderloin::Commands.halt
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "destroy [--file <tenderfile>]", "Shuts down and deletes the VM"
|
28
|
+
def destroy()
|
29
|
+
setup
|
30
|
+
Tenderloin::Commands.destroy
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "box [--file <tenderfile>]", "Manages base boxes"
|
34
|
+
# TODO - make the box command use real args/a subcommand
|
35
|
+
def box(arg1=nil, arg2=nil, arg3=nil, arg4=nil)
|
36
|
+
setup
|
37
|
+
Tenderloin::Commands.box([arg1, arg2, arg3, arg4].compact)
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "init [--file <tenderfile>]", "Creates a new Tenderfile"
|
41
|
+
def init()
|
42
|
+
setup
|
43
|
+
Tenderloin::Commands.init
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "reload [--file <tenderfile>]", "Reboots & re-provisions the VM"
|
47
|
+
def reload()
|
48
|
+
setup
|
49
|
+
Tenderloin::Commands.reload
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "ssh [--file <tenderfile>]", "SSH's in to the VM"
|
53
|
+
def ssh()
|
54
|
+
setup
|
55
|
+
Tenderloin::Commands.ssh
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
# Contains all the command-line commands invoked by the
|
3
|
+
# binaries. Having them all in one location assists with
|
4
|
+
# documentation and also takes the commands out of some of
|
5
|
+
# the other classes.
|
6
|
+
class Commands
|
7
|
+
extend Tenderloin::Util
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Initializes a directory for use with tenderloin. This command copies an
|
11
|
+
# initial `Tenderfile` into the current working directory so you can
|
12
|
+
# begin using tenderloin. The configuration file contains some documentation
|
13
|
+
# to get you started.
|
14
|
+
def init
|
15
|
+
rootfile_path = File.join(Dir.pwd, $ROOTFILE_NAME)
|
16
|
+
if File.exist?(rootfile_path)
|
17
|
+
error_and_exit(<<-error)
|
18
|
+
It looks like this directory is already setup for tenderloin! (A #{$ROOTFILE_NAME}
|
19
|
+
already exists.)
|
20
|
+
error
|
21
|
+
end
|
22
|
+
|
23
|
+
# Copy over the rootfile template into this directory
|
24
|
+
FileUtils.cp(File.join(PROJECT_ROOT, "templates", $ROOTFILE_NAME), rootfile_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Bring up a tenderloin instance. This handles everything from importing
|
28
|
+
# the base VM, setting up shared folders, forwarded ports, etc to
|
29
|
+
# provisioning the instance with chef. {up} also starts the instance,
|
30
|
+
# running it in the background.
|
31
|
+
def up
|
32
|
+
Env.load!
|
33
|
+
|
34
|
+
if Env.persisted_vm
|
35
|
+
logger.info "VM already created. Starting VM if its not already running..."
|
36
|
+
Env.persisted_vm.start
|
37
|
+
else
|
38
|
+
Env.require_box
|
39
|
+
VM.execute!(Actions::VM::Up)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Tear down a tenderloin instance. This not only shuts down the instance
|
44
|
+
# (if its running), but also deletes it from the system, including the
|
45
|
+
# hard disks associated with it.
|
46
|
+
#
|
47
|
+
# This command requires that an instance already be brought up with
|
48
|
+
# `tenderloin up`.
|
49
|
+
def destroy
|
50
|
+
Env.load!
|
51
|
+
Env.require_persisted_vm
|
52
|
+
Env.persisted_vm.destroy
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reload the environment. This is almost equivalent to the {up} command
|
56
|
+
# except that it doesn't import the VM and do the initialize bootstrapping
|
57
|
+
# of the instance. Instead, it forces a shutdown (if its running) of the
|
58
|
+
# VM, updates the metadata (shared folders, forwarded ports), restarts
|
59
|
+
# the VM, and then reruns the provisioning if enabled.
|
60
|
+
def reload
|
61
|
+
Env.load!
|
62
|
+
Env.require_persisted_vm
|
63
|
+
Env.persisted_vm.execute!(Actions::VM::Reload)
|
64
|
+
end
|
65
|
+
|
66
|
+
# SSH into the tenderloin instance. This will setup an SSH connection into
|
67
|
+
# the tenderloin instance, replacing the running ruby process with the SSH
|
68
|
+
# connection.
|
69
|
+
#
|
70
|
+
# This command requires that an instance already be brought up with
|
71
|
+
# `tenderloin up`.
|
72
|
+
def ssh
|
73
|
+
Env.load!
|
74
|
+
Env.require_persisted_vm
|
75
|
+
SSH.connect :host => Env.persisted_vm.fusion_vm.ip
|
76
|
+
end
|
77
|
+
|
78
|
+
# Halts a running tenderloin instance. This forcibly halts the instance;
|
79
|
+
# it is the equivalent of pulling the power on a machine. The instance
|
80
|
+
# can be restarted again with {up}.
|
81
|
+
#
|
82
|
+
# This command requires than an instance already be brought up with
|
83
|
+
# `tenderloin up`.
|
84
|
+
def halt
|
85
|
+
Env.load!
|
86
|
+
Env.require_persisted_vm
|
87
|
+
Env.persisted_vm.execute!(Actions::VM::Halt)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Manages the `tenderloin box` command, allowing the user to add
|
91
|
+
# and remove boxes. This single command, given an array, determines
|
92
|
+
# which action to take and calls the respective action method
|
93
|
+
# (see {box_add} and {box_remove})
|
94
|
+
def box(argv)
|
95
|
+
Env.load!
|
96
|
+
|
97
|
+
sub_commands = ["list", "add", "remove"]
|
98
|
+
|
99
|
+
if !sub_commands.include?(argv[0])
|
100
|
+
error_and_exit(<<-error)
|
101
|
+
Please specify a valid action to take on the boxes, either
|
102
|
+
`add` or `remove`. Examples:
|
103
|
+
|
104
|
+
tenderloin box add name uri
|
105
|
+
tenderloin box remove name
|
106
|
+
error
|
107
|
+
end
|
108
|
+
|
109
|
+
send("box_#{argv[0]}", *argv[1..-1])
|
110
|
+
end
|
111
|
+
|
112
|
+
# Lists all added boxes
|
113
|
+
def box_list
|
114
|
+
boxes = Box.all.sort
|
115
|
+
|
116
|
+
wrap_output do
|
117
|
+
if !boxes.empty?
|
118
|
+
puts "Installed Tenderloin Boxes:\n\n"
|
119
|
+
boxes.each do |box|
|
120
|
+
puts box
|
121
|
+
end
|
122
|
+
else
|
123
|
+
puts "No Tenderloin Boxes Added!"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Adds a box to the local filesystem, given a URI.
|
129
|
+
def box_add(name, path)
|
130
|
+
Box.add(name, path)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Removes a box.
|
134
|
+
def box_remove(name)
|
135
|
+
box = Box.find(name)
|
136
|
+
if box.nil?
|
137
|
+
error_and_exit(<<-error)
|
138
|
+
The box you're attempting to remove does not exist!
|
139
|
+
error
|
140
|
+
return # for tests
|
141
|
+
end
|
142
|
+
|
143
|
+
box.destroy
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def act_on_vm(&block)
|
149
|
+
yield Env.persisted_vm
|
150
|
+
Env.persisted_vm.execute!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|