tenderloin 0.2.0

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