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
@@ -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