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,144 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
def self.config
|
3
|
+
Config.config
|
4
|
+
end
|
5
|
+
|
6
|
+
class Config
|
7
|
+
@config = nil
|
8
|
+
@config_runners = []
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def reset!
|
12
|
+
@config = nil
|
13
|
+
config_runners.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def config
|
17
|
+
@config ||= Config::Top.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_runners
|
21
|
+
@config_runners ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(&block)
|
25
|
+
config_runners << block
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute!
|
29
|
+
config_runners.each do |block|
|
30
|
+
block.call(config)
|
31
|
+
end
|
32
|
+
|
33
|
+
config.loaded!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Config
|
39
|
+
class Base
|
40
|
+
def [](key)
|
41
|
+
send(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_json
|
45
|
+
instance_variables_hash.to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
def instance_variables_hash
|
49
|
+
instance_variables.inject({}) do |acc, iv|
|
50
|
+
acc[iv.to_s[1..-1].to_sym] = instance_variable_get(iv)
|
51
|
+
acc
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class SSHConfig < Base
|
57
|
+
attr_accessor :username
|
58
|
+
attr_accessor :password
|
59
|
+
attr_accessor :host
|
60
|
+
attr_accessor :max_tries
|
61
|
+
attr_accessor :timeout
|
62
|
+
end
|
63
|
+
|
64
|
+
class VMConfig < Base
|
65
|
+
attr_accessor :box
|
66
|
+
attr_accessor :box_vmx
|
67
|
+
attr_accessor :project_directory
|
68
|
+
attr_accessor :hd_location
|
69
|
+
|
70
|
+
|
71
|
+
def initialize
|
72
|
+
end
|
73
|
+
|
74
|
+
def hd_location=(val)
|
75
|
+
raise Exception.new "disk_storage must be set to a directory" unless File.directory?(val)
|
76
|
+
@hd_location=val
|
77
|
+
end
|
78
|
+
|
79
|
+
def base
|
80
|
+
File.expand_path(@base)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class PackageConfig < Base
|
85
|
+
attr_accessor :name
|
86
|
+
attr_accessor :extension
|
87
|
+
end
|
88
|
+
|
89
|
+
class ChefConfig < Base
|
90
|
+
attr_accessor :cookbooks_path
|
91
|
+
attr_accessor :provisioning_path
|
92
|
+
attr_accessor :json
|
93
|
+
attr_accessor :enabled
|
94
|
+
|
95
|
+
def initialize
|
96
|
+
@enabled = false
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_json
|
100
|
+
# Overridden so that the 'json' key could be removed, since its just
|
101
|
+
# merged into the config anyways
|
102
|
+
data = instance_variables_hash
|
103
|
+
data.delete(:json)
|
104
|
+
data.to_json
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class TenderloinConfig < Base
|
109
|
+
attr_accessor :dotfile_name
|
110
|
+
attr_accessor :log_output
|
111
|
+
attr_accessor :home
|
112
|
+
|
113
|
+
def home
|
114
|
+
File.expand_path(@home)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Top < Base
|
119
|
+
attr_reader :package
|
120
|
+
attr_reader :ssh
|
121
|
+
attr_reader :vm
|
122
|
+
attr_reader :chef
|
123
|
+
attr_reader :tenderloin
|
124
|
+
|
125
|
+
def initialize
|
126
|
+
@ssh = SSHConfig.new
|
127
|
+
@vm = VMConfig.new
|
128
|
+
@chef = ChefConfig.new
|
129
|
+
@tenderloin = TenderloinConfig.new
|
130
|
+
@package = PackageConfig.new
|
131
|
+
|
132
|
+
@loaded = false
|
133
|
+
end
|
134
|
+
|
135
|
+
def loaded?
|
136
|
+
@loaded
|
137
|
+
end
|
138
|
+
|
139
|
+
def loaded!
|
140
|
+
@loaded = true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Downloaders
|
3
|
+
# Represents a base class for a downloader. A downloader handles
|
4
|
+
# downloading a box file to a temporary file.
|
5
|
+
class Base
|
6
|
+
include Tenderloin::Util
|
7
|
+
|
8
|
+
# Downloads the source file to the destination file. It is up to
|
9
|
+
# implementors of this class to handle the logic.
|
10
|
+
def download!(source_url, destination_file); end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Downloaders
|
3
|
+
# "Downloads" a file to a temporary file. Basically, this downloader
|
4
|
+
# simply does a file copy.
|
5
|
+
class File < Base
|
6
|
+
BUFFERSIZE = 1048576 # 1 MB
|
7
|
+
|
8
|
+
def download!(source_url, destination_file)
|
9
|
+
# For now we read the contents of one into a buffer
|
10
|
+
# and copy it into the other. In the future, we should do
|
11
|
+
# a system-level file copy (FileUtils.cp).
|
12
|
+
open(source_url) do |f|
|
13
|
+
loop do
|
14
|
+
break if f.eof?
|
15
|
+
destination_file.write(f.read(BUFFERSIZE))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Downloaders
|
3
|
+
# Downloads a file from an HTTP URL to a temporary file. This
|
4
|
+
# downloader reports its progress to stdout while downloading.
|
5
|
+
class HTTP < Base
|
6
|
+
# ANSI escape code to clear lines from cursor to end of line
|
7
|
+
CL_RESET = "\r\e[0K"
|
8
|
+
|
9
|
+
def download!(source_url, destination_file)
|
10
|
+
Net::HTTP.get_response(URI.parse(source_url)) do |response|
|
11
|
+
total = response.content_length
|
12
|
+
progress = 0
|
13
|
+
segment_count = 0
|
14
|
+
|
15
|
+
response.read_body do |segment|
|
16
|
+
# Report the progress out
|
17
|
+
progress += segment.length
|
18
|
+
segment_count += 1
|
19
|
+
|
20
|
+
# Progress reporting is limited to every 25 segments just so
|
21
|
+
# we're not constantly updating
|
22
|
+
if segment_count % 25 == 0
|
23
|
+
report_progress(progress, total)
|
24
|
+
segment_count = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
# Store the segment
|
28
|
+
destination_file.write(segment)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
complete_progress
|
33
|
+
end
|
34
|
+
|
35
|
+
def report_progress(progress, total)
|
36
|
+
percent = (progress.to_f / total.to_f) * 100
|
37
|
+
print "#{CL_RESET}Download Progress: #{percent.to_i}% (#{progress} / #{total})"
|
38
|
+
$stdout.flush
|
39
|
+
end
|
40
|
+
|
41
|
+
def complete_progress
|
42
|
+
# Just clear the line back out
|
43
|
+
print "#{CL_RESET}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
class Env
|
3
|
+
BOXFILE_NAME = "Tenderfile"
|
4
|
+
HOME_SUBDIRS = ["tmp", "boxes"]
|
5
|
+
|
6
|
+
# Initialize class variables used
|
7
|
+
@@persisted_vm = nil
|
8
|
+
@@root_path = nil
|
9
|
+
@@box = nil
|
10
|
+
|
11
|
+
extend Tenderloin::Util
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def box; @@box; end
|
15
|
+
def persisted_vm; @@persisted_vm; end
|
16
|
+
def root_path; @@root_path; end
|
17
|
+
def dotfile_path
|
18
|
+
File.join(root_path, $ROOTFILE_NAME + ".loinstate")
|
19
|
+
end
|
20
|
+
def home_path; File.expand_path(Tenderloin.config.tenderloin.home); end
|
21
|
+
def tmp_path; File.join(home_path, "tmp"); end
|
22
|
+
def boxes_path; File.join(home_path, "boxes"); end
|
23
|
+
def vms_path; File.join(home_path, "vms"); end
|
24
|
+
|
25
|
+
def load!
|
26
|
+
load_root_path!
|
27
|
+
load_config!
|
28
|
+
load_home_directory!
|
29
|
+
load_box!
|
30
|
+
load_vm!
|
31
|
+
end
|
32
|
+
|
33
|
+
def load_config!
|
34
|
+
# Prepare load paths for config files
|
35
|
+
load_paths = [File.join(PROJECT_ROOT, "config", "default.rb")]
|
36
|
+
load_paths << File.join(box.directory, BOXFILE_NAME) if box
|
37
|
+
load_paths << File.join(root_path, $ROOTFILE_NAME) if root_path
|
38
|
+
|
39
|
+
# Then clear out the old data
|
40
|
+
Config.reset!
|
41
|
+
|
42
|
+
load_paths.each do |path|
|
43
|
+
logger.info "Loading config from #{path}..."
|
44
|
+
load path if File.exist?(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Execute the configurations
|
48
|
+
Config.execute!
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_home_directory!
|
52
|
+
home_dir = File.expand_path(Tenderloin.config.tenderloin.home)
|
53
|
+
|
54
|
+
dirs = HOME_SUBDIRS.collect { |path| File.join(home_dir, path) }
|
55
|
+
dirs.unshift(home_dir)
|
56
|
+
|
57
|
+
dirs.each do |dir|
|
58
|
+
next if File.directory?(dir)
|
59
|
+
|
60
|
+
logger.info "Creating home directory since it doesn't exist: #{dir}"
|
61
|
+
FileUtils.mkdir_p(dir)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_box!
|
66
|
+
return unless root_path
|
67
|
+
|
68
|
+
@@box = Box.find(Tenderloin.config.vm.box) if Tenderloin.config.vm.box
|
69
|
+
|
70
|
+
if @@box
|
71
|
+
logger.info("Reloading configuration to account for loaded box...")
|
72
|
+
load_config!
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_vm!
|
77
|
+
return if !root_path || !File.file?(dotfile_path)
|
78
|
+
|
79
|
+
File.open(dotfile_path) do |f|
|
80
|
+
@@persisted_vm = Tenderloin::VM.find(f.read)
|
81
|
+
end
|
82
|
+
rescue Errno::ENOENT
|
83
|
+
@@persisted_vm = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def persist_vm(vm_id)
|
87
|
+
File.open(dotfile_path, 'w+') do |f|
|
88
|
+
f.write(vm_id)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_root_path!(path=nil)
|
93
|
+
path ||= Pathname.new(Dir.pwd)
|
94
|
+
|
95
|
+
# Stop if we're at the root. 2nd regex matches windows drives
|
96
|
+
# such as C:. and Z:. Portability of this check will need to be
|
97
|
+
# researched.
|
98
|
+
return false if path.to_s == '/' || path.to_s =~ /^[A-Z]:\.$/
|
99
|
+
|
100
|
+
file = "#{path}/#{$ROOTFILE_NAME}"
|
101
|
+
if File.exist?(file)
|
102
|
+
@@root_path = path.to_s
|
103
|
+
return true
|
104
|
+
end
|
105
|
+
|
106
|
+
load_root_path!(path.parent)
|
107
|
+
end
|
108
|
+
|
109
|
+
def require_root_path
|
110
|
+
if !root_path
|
111
|
+
error_and_exit(<<-msg)
|
112
|
+
A `#{$ROOTFILE_NAME}` was not found! This file is required for tenderloin to run
|
113
|
+
since it describes the expected environment that tenderloin is supposed
|
114
|
+
to manage. Please create a #{$ROOTFILE_NAME} and place it in your project
|
115
|
+
root.
|
116
|
+
msg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def require_box
|
121
|
+
require_root_path
|
122
|
+
|
123
|
+
if !box
|
124
|
+
if !Tenderloin.config.vm.box
|
125
|
+
error_and_exit(<<-msg)
|
126
|
+
No base box was specified! A base box is required as a staring point
|
127
|
+
for every tenderloin virtual machine. Please specify one in your Tenderfile
|
128
|
+
using `config.vm.box`
|
129
|
+
msg
|
130
|
+
else
|
131
|
+
error_and_exit(<<-msg)
|
132
|
+
Specified box `#{Tenderloin.config.vm.box}` does not exist!
|
133
|
+
|
134
|
+
The box must be added through the `tenderloin box add` command. Please view
|
135
|
+
the documentation associated with the command for more information.
|
136
|
+
msg
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def require_persisted_vm
|
142
|
+
require_root_path
|
143
|
+
|
144
|
+
if !persisted_vm
|
145
|
+
error_and_exit(<<-error)
|
146
|
+
The task you're trying to run requires that the tenderloin environment
|
147
|
+
already be created, but unfortunately this tenderloin still appears to
|
148
|
+
have no box! You can setup the environment by setting up your
|
149
|
+
#{$ROOTFILE_NAME} and running `tenderloin up`
|
150
|
+
error
|
151
|
+
return
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
class FusionVM
|
3
|
+
VMRUN = "/Applications/VMware\\ Fusion.app/Contents/Library/vmrun"
|
4
|
+
|
5
|
+
def initialize(vmx)
|
6
|
+
@vmx = vmx
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(cmd, opts='')
|
10
|
+
retrycount = 0
|
11
|
+
while true
|
12
|
+
res = `#{VMRUN} #{cmd} #{@vmx} #{opts}`
|
13
|
+
if $? == 0
|
14
|
+
return res
|
15
|
+
else
|
16
|
+
if res =~ /VMware Tools are not running/
|
17
|
+
sleep 1; next unless retrycount > 10
|
18
|
+
end
|
19
|
+
raise "Error running vmrun command #{cmd}: " + res
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def start_fusion
|
25
|
+
# Ensure fusion is running.
|
26
|
+
`if [[ -z $(pgrep 'VMware Fusion') ]]; then open /Applications/VMware\\ Fusion.app ; sleep 5 ; fi`
|
27
|
+
end
|
28
|
+
|
29
|
+
def running?()
|
30
|
+
`#{VMRUN} list | grep "#{@vmx}"`
|
31
|
+
$? == 0 ? true : false
|
32
|
+
end
|
33
|
+
|
34
|
+
def start(opts = {})
|
35
|
+
gui_opt = opts[:headless] == true ? "nogui" : "gui"
|
36
|
+
run('start', gui_opt)
|
37
|
+
end
|
38
|
+
|
39
|
+
def stop(opts = {})
|
40
|
+
hard_opt = opts[:force] == true ? "hard" : "soft"
|
41
|
+
run 'stop', hard_opt
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete()
|
45
|
+
run 'deleteVM'
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_guest_var(var)
|
49
|
+
run 'readVariable', 'guestVar ' + var
|
50
|
+
end
|
51
|
+
|
52
|
+
def ip
|
53
|
+
ip = get_guest_var('ip').strip
|
54
|
+
unless ip =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/
|
55
|
+
mac_address = VMXFile.load(@vmx)["ethernet0.generatedAddress"]
|
56
|
+
ip = dhcp_leases[mac_address]
|
57
|
+
end
|
58
|
+
ip
|
59
|
+
end
|
60
|
+
|
61
|
+
def enable_shared_folders
|
62
|
+
run 'enableSharedFolders'
|
63
|
+
end
|
64
|
+
|
65
|
+
def share_folder(name, hostpath)
|
66
|
+
run 'addSharedFolder', "#{name} #{hostpath}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def dhcp_leases
|
70
|
+
mac_ip = {}
|
71
|
+
curLeaseIp = nil
|
72
|
+
Dir['/var/db/vmware/vmnet-dhcpd*.leases'].each do |f|
|
73
|
+
File.open(f).each do |line|
|
74
|
+
case line
|
75
|
+
when /lease (.*) \{/
|
76
|
+
curLeaseIp = $1
|
77
|
+
when /hardware ethernet (.*);/
|
78
|
+
mac_ip[$1] = curLeaseIp
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
mac_ip
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
class SSH
|
3
|
+
SCRIPT = File.join(File.dirname(__FILE__), '..', '..', 'script', 'tenderloin-ssh-expect.sh')
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def connect(opts={})
|
7
|
+
options = {}
|
8
|
+
[:host, :password, :username].each do |param|
|
9
|
+
options[param] = opts[param] || Tenderloin.config.ssh.send(param)
|
10
|
+
end
|
11
|
+
|
12
|
+
Kernel.exec "#{SCRIPT} #{options[:username]} #{options[:password]} #{options[:host]} #{port(opts)}".strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(ip)
|
16
|
+
Net::SSH.start(ip, Tenderloin.config[:ssh][:username], :port => port, :password => Tenderloin.config[:ssh][:password]) do |ssh|
|
17
|
+
yield ssh
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def upload!(from, to)
|
22
|
+
execute do |ssh|
|
23
|
+
scp = Net::SCP.new(ssh)
|
24
|
+
scp.upload!(from, to)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def up?(ip)
|
29
|
+
check_thread = Thread.new do
|
30
|
+
begin
|
31
|
+
Thread.current[:result] = false
|
32
|
+
Net::SSH.start(ip, Tenderloin.config.ssh.username, :port => port, :password => Tenderloin.config.ssh.password, :timeout => Tenderloin.config.ssh.timeout) do |ssh|
|
33
|
+
Thread.current[:result] = true
|
34
|
+
end
|
35
|
+
rescue Errno::ECONNREFUSED, Net::SSH::Disconnect
|
36
|
+
# False, its defaulted above
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
check_thread.join(Tenderloin.config.ssh.timeout)
|
41
|
+
return check_thread[:result]
|
42
|
+
end
|
43
|
+
|
44
|
+
def port(opts={})
|
45
|
+
opts[:port] || 22
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
module Util
|
3
|
+
def self.included(base)
|
4
|
+
base.extend Tenderloin::Util
|
5
|
+
end
|
6
|
+
|
7
|
+
def wrap_output
|
8
|
+
puts "====================================================================="
|
9
|
+
yield
|
10
|
+
puts "====================================================================="
|
11
|
+
end
|
12
|
+
|
13
|
+
def error_and_exit(error)
|
14
|
+
abort <<-error
|
15
|
+
=====================================================================
|
16
|
+
Tenderloin experienced an error!
|
17
|
+
|
18
|
+
#{error.chomp}
|
19
|
+
=====================================================================
|
20
|
+
error
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger
|
24
|
+
Logger.singleton_logger
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Logger < ::Logger
|
29
|
+
@@singleton_logger = nil
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def singleton_logger
|
33
|
+
# TODO: Buffer messages until config is loaded, then output them?
|
34
|
+
if Tenderloin.config.loaded?
|
35
|
+
@@singleton_logger ||= Tenderloin::Logger.new(Tenderloin.config.tenderloin.log_output)
|
36
|
+
else
|
37
|
+
Tenderloin::Logger.new(nil)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset_logger!
|
42
|
+
@@singleton_logger = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def format_message(level, time, progname, msg)
|
47
|
+
"[#{level} #{time.strftime('%m-%d-%Y %X')}] Tenderloin: #{msg}\n"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
class VM < Actions::Runner
|
3
|
+
include Tenderloin::Util
|
4
|
+
|
5
|
+
attr_accessor :vm_id
|
6
|
+
attr_accessor :from
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Finds a virtual machine by a given UUID and either returns
|
10
|
+
# a Tenderloin::VM object or returns nil.
|
11
|
+
def find(uuid)
|
12
|
+
# TODO - just validate the existence - there's no 'loading' needed
|
13
|
+
new(uuid) if File.exists? File.join(Tenderloin::Env.vms_path, uuid, uuid + ".vmx")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(vm=nil)
|
18
|
+
@vm_id = vm
|
19
|
+
end
|
20
|
+
|
21
|
+
def package(out_path, include_files=[])
|
22
|
+
add_action(Actions::VM::Export)
|
23
|
+
add_action(Actions::VM::Package, out_path, include_files)
|
24
|
+
execute!
|
25
|
+
end
|
26
|
+
|
27
|
+
def vmx_path
|
28
|
+
File.join(Tenderloin::Env.vms_path, @vm_id, @vm_id + ".vmx")
|
29
|
+
end
|
30
|
+
|
31
|
+
def fusion_vm
|
32
|
+
@fusion_vm ||= FusionVM.new(vmx_path) if vmx_path
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
return if running?
|
37
|
+
|
38
|
+
execute!(Actions::VM::Start)
|
39
|
+
end
|
40
|
+
|
41
|
+
def running?
|
42
|
+
fusion_vm.running?
|
43
|
+
end
|
44
|
+
|
45
|
+
def destroy
|
46
|
+
execute!(Actions::VM::Destroy)
|
47
|
+
end
|
48
|
+
|
49
|
+
def suspend
|
50
|
+
execute!(Actions::VM::Suspend)
|
51
|
+
end
|
52
|
+
|
53
|
+
def resume
|
54
|
+
execute!(Actions::VM::Resume)
|
55
|
+
end
|
56
|
+
|
57
|
+
def saved?
|
58
|
+
@vm.saved?
|
59
|
+
end
|
60
|
+
|
61
|
+
def powered_off?; @vm.powered_off? end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Tenderloin
|
2
|
+
class VMXFile
|
3
|
+
|
4
|
+
def self.load(filename)
|
5
|
+
data = {}
|
6
|
+
File.open(filename).each do |line|
|
7
|
+
parts = line.split('=')
|
8
|
+
data[parts[0].strip] = parts[1].strip.gsub!(/^"(.*?)"$/,'\1')
|
9
|
+
end
|
10
|
+
data
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.write(filename, data)
|
14
|
+
File.open(filename, 'w') do |f|
|
15
|
+
data.each do |k,v|
|
16
|
+
f.puts "#{k} = \"#{v}\""
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_vmx_data(filename, &block)
|
22
|
+
data = load(filename)
|
23
|
+
block.call(data)
|
24
|
+
write(filename, data)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/tenderloin.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$:.unshift(libdir)
|
3
|
+
PROJECT_ROOT = File.join(libdir, '..') unless defined?(PROJECT_ROOT)
|
4
|
+
|
5
|
+
# The libs which must be loaded prior to the rest
|
6
|
+
%w{tempfile open-uri json pathname logger uri net/http net/ssh archive/tar/minitar
|
7
|
+
net/scp fileutils tenderloin/util tenderloin/actions/base tenderloin/downloaders/base tenderloin/actions/runner}.each do |f|
|
8
|
+
require f
|
9
|
+
end
|
10
|
+
|
11
|
+
# Glob require the rest
|
12
|
+
Dir[File.join(PROJECT_ROOT, "lib", "tenderloin", "**", "*.rb")].each do |f|
|
13
|
+
require f
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/expect
|
2
|
+
|
3
|
+
set uname [lrange $argv 0 0]
|
4
|
+
set password [lrange $argv 1 1]
|
5
|
+
set host [lrange $argv 2 2]
|
6
|
+
set port [lrange $argv 3 3]
|
7
|
+
|
8
|
+
if { $port != "" } {
|
9
|
+
set port_option "-p $port"
|
10
|
+
} else {
|
11
|
+
set port_option ""
|
12
|
+
}
|
13
|
+
|
14
|
+
spawn ssh $port_option -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $uname@$host
|
15
|
+
|
16
|
+
set timeout 30
|
17
|
+
expect "*password: " {
|
18
|
+
send "$password\r"
|
19
|
+
} timeout {
|
20
|
+
send_user "Error connecting"
|
21
|
+
}
|
22
|
+
|
23
|
+
interact
|