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