fig 0.1.24-universal-darwin9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fig.rb ADDED
File without changes
@@ -0,0 +1,171 @@
1
+ module Fig
2
+
3
+ # This class manages the program's state, including the value of all environment
4
+ # variables, and which packages have already been applied
5
+ class Environment
6
+ DEFAULT_VERSION_NAME = "current"
7
+
8
+ def initialize(os, repository, variables)
9
+ @os = os
10
+ @repository = repository
11
+ @variables = variables
12
+ @retrieve_vars = {}
13
+ @packages = {}
14
+ @applied_configs = {}
15
+ end
16
+
17
+ # Returns the value of an envirionment variable
18
+ def [](name)
19
+ @variables[name]
20
+ end
21
+
22
+ # Indicates that the values from a particular envrionment variable path should
23
+ # be copied to a local directory
24
+ def add_retrieve(name, path)
25
+ @retrieve_vars[name] = path
26
+ end
27
+
28
+ def register_package(package)
29
+ name = package.package_name
30
+ if @packages[name]
31
+ puts "Package already exists with name: #{name}"
32
+ exit 10
33
+ end
34
+ @packages[name] = package
35
+ end
36
+
37
+ def apply_config(package, config_name)
38
+ if (@applied_configs[package.package_name] ||= []).member?(config_name)
39
+ return
40
+ end
41
+ config = package[config_name]
42
+ config.statements.each { |stmt| apply_config_statement(package, stmt) }
43
+ @applied_configs[package.package_name] << config_name
44
+ end
45
+
46
+ def execute_shell(command)
47
+ with_environment do
48
+ yield command.map{|arg| expand_arg(arg)}
49
+ end
50
+ end
51
+
52
+ def execute_config(base_package, package_name, config_name, version_name, args)
53
+ package = lookup_package(package_name || base_package.package_name, version_name)
54
+ result = nil
55
+ commands = package[config_name || "default"].commands
56
+ with_environment do
57
+ # todo nil check
58
+ commands.each do |command|
59
+ result = yield expand_arg("#{command.command} #{args.join(' ')}").gsub("@",package.directory).split(" ")
60
+ end
61
+ end
62
+ result
63
+ end
64
+
65
+ def apply_config_statement(base_package, statement)
66
+ case statement
67
+ when Path
68
+ append_variable(base_package, statement.name, statement.value)
69
+ when Set
70
+ set_variable(base_package, statement.name, statement.value)
71
+ when Include
72
+ include_config(base_package, statement.package_name, statement.config_name, statement.version_name)
73
+ when Command
74
+ # ignore
75
+ else
76
+ fail "Unexpected statement: #{statement}"
77
+ end
78
+ end
79
+
80
+ def include_config(base_package, package_name, config_name, version_name)
81
+ package = lookup_package(package_name || base_package.package_name, version_name)
82
+ apply_config(package, config_name || "default")
83
+ end
84
+
85
+ def direct_retrieve(package_name, source_path, target_path)
86
+ package = lookup_package(package_name, nil)
87
+ FileUtils.mkdir_p(target_path)
88
+ FileUtils.cp_r(File.join(package.directory, source_path, '.'), target_path)
89
+ end
90
+
91
+ private
92
+
93
+ def set_variable(base_package, name, value)
94
+ @variables[name] = expand_value(base_package, name, value)
95
+ end
96
+
97
+ def append_variable(base_package, name, value)
98
+ value = expand_value(base_package, name, value)
99
+ # TODO: converting all environment variables to upcase is not a robust
100
+ # comparison. It also assumes all env vars will be in upcase
101
+ # in package.fig
102
+ prev = nil
103
+ @variables.each do |key, val|
104
+ if key.upcase == name.upcase
105
+ name = key
106
+ prev = val
107
+ end
108
+ end
109
+ if prev
110
+ @variables[name] = value + File::PATH_SEPARATOR + prev
111
+ else
112
+ @variables[name] = value
113
+ end
114
+ end
115
+
116
+ def with_environment
117
+ old_env = {}
118
+ begin
119
+ @variables.each { |key,value| old_env[key] = ENV[key]; ENV[key] = value }
120
+ yield
121
+ ensure
122
+ old_env.each { |key,value| ENV[key] = value }
123
+ end
124
+ end
125
+
126
+ def lookup_package(package_name, version_name)
127
+ package = @packages[package_name]
128
+ if package.nil?
129
+ package = @repository.load_package(package_name, version_name || DEFAULT_VERSION_NAME)
130
+ @packages[package_name] = package
131
+ elsif version_name && version_name != package.version_name
132
+ puts "Version mismatch: #{package_name}"
133
+ exit 10
134
+ end
135
+ package
136
+ end
137
+
138
+ # Replace @ symbol with the package's directory
139
+ def expand_value(base_package, name, value)
140
+ return value unless base_package && base_package.package_name
141
+ file = value.gsub(/\@/, base_package.directory)
142
+ if @retrieve_vars.member?(name)
143
+ # A '//' in the source file's path tells us to preserve path information
144
+ # after the '//' when doing a retrieve.
145
+ if file.split('//').size > 1
146
+ preserved_path = file.split('//').last
147
+ target = File.join(@retrieve_vars[name].gsub(/\[package\]/, base_package.package_name), preserved_path)
148
+ else
149
+ target = File.join(@retrieve_vars[name].gsub(/\[package\]/, base_package.package_name), File.basename(file))
150
+ end
151
+ unless @os.exist?(target) && @os.mtime(target) >= @os.mtime(file)
152
+ @os.log_info("retrieving #{target}")
153
+ @os.copy(file, target)
154
+ end
155
+ file = target
156
+ end
157
+ file
158
+ end
159
+
160
+ def expand_arg(arg)
161
+ arg.gsub(/\@([a-zA-Z0-9\-\.]+)/) do |match|
162
+ package = @packages[$1]
163
+ if package.nil?
164
+ puts "Package not found: #{$1}"
165
+ exit 10
166
+ end
167
+ package.directory
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,141 @@
1
+ require 'fig/package'
2
+
3
+ module Fig
4
+
5
+ grammar Fig
6
+ rule package
7
+ ws statements:(package_statement*) {
8
+ def to_package(package_name, version_name, directory)
9
+ Package.new(package_name, version_name, directory, statements.elements.map { |statement| statement.to_package_statement })
10
+ end
11
+ }
12
+ end
13
+
14
+ rule package_statement
15
+ archive / resource / retrieve / config
16
+ end
17
+
18
+ rule archive
19
+ "archive" ws url {
20
+ def to_package_statement
21
+ Archive.new(url.value.text_value)
22
+ end
23
+ }
24
+ end
25
+
26
+ rule resource
27
+ "resource" ws url {
28
+ def to_package_statement
29
+ Resource.new(url.value.text_value)
30
+ end
31
+ }
32
+ end
33
+
34
+ rule retrieve
35
+ "retrieve" ws var:[@a-zA-Z0-9/\._]+ "->" path:[a-zA-Z0-9/\.\-\[\]]+ ws {
36
+ def to_package_statement
37
+ Retrieve.new(var.text_value, path.text_value)
38
+ end
39
+ }
40
+ end
41
+
42
+ rule install
43
+ "install" ws statements:config_statement* "end" ws {
44
+ def to_package_statement
45
+ Install.new(statements.elements.map { |statement| statement.to_config_statement })
46
+ end
47
+ }
48
+ end
49
+
50
+ rule config
51
+ "config" ws config_name ws statements:config_statement* "end" ws {
52
+ def to_package_statement
53
+ Configuration.new(config_name.text_value, statements.elements.map { |statement| statement.to_config_statement })
54
+ end
55
+ }
56
+ end
57
+
58
+ rule config_statement
59
+ include / command / path / set
60
+ end
61
+
62
+ rule include
63
+ "include" ws descriptor {
64
+ def to_config_statement
65
+ package = descriptor.respond_to?(:package) ? descriptor.package.text_value : nil
66
+ config = descriptor.get_config
67
+ version = descriptor.get_version
68
+ Include.new(package, config, version)
69
+ end
70
+ }
71
+ end
72
+
73
+ rule path
74
+ ("append" / "path" / "add") ws name:[a-zA-Z0-9_]+ "=" value:[@a-zA-Z0-9/\-\\._]+ ws {
75
+ def to_config_statement
76
+ Path.new(name.text_value, value.text_value)
77
+ end
78
+ }
79
+ end
80
+
81
+ rule set
82
+ "set" ws name:[a-zA-Z0-9_]+ "=" value:[@a-zA-Z0-9/\-\\._]+ ws {
83
+ def to_config_statement
84
+ Set.new(name.text_value, value.text_value)
85
+ end
86
+ }
87
+ end
88
+
89
+ rule command
90
+ "command" ws string {
91
+ def to_config_statement
92
+ Command.new(string.value.text_value)
93
+ end
94
+ }
95
+ end
96
+
97
+ rule string
98
+ '"' value:(!'"' . )* '"' ws
99
+ end
100
+
101
+ rule descriptor
102
+ ((package:[a-zA-Z0-9.-]+ ("/" version:[a-zA-Z0-9_\-.]+)? (":" config:config_name)? ws) /
103
+ (":" config:config_name ws)) {
104
+ def get_version
105
+ elements.each do |element|
106
+ if element.respond_to?(:version)
107
+ return element.version.text_value
108
+ end
109
+ end
110
+ nil
111
+ end
112
+ def get_config
113
+ return self.config.text_value if self.respond_to?(:config)
114
+ elements.each do |element|
115
+ if element.respond_to?(:config)
116
+ return element.config.text_value
117
+ end
118
+ end
119
+ nil
120
+ end
121
+ }
122
+ end
123
+
124
+ rule config_name
125
+ [a-zA-Z0-9_\-.]+
126
+ end
127
+
128
+ rule name
129
+ value:[a-zA-Z0-9]+ ws
130
+ end
131
+
132
+ rule url
133
+ (value:[a-zA-Z0-9:/\-\\._\*]+ ws) / ('"' value:[a-zA-Z0-9:/\-\\._]+ '"' ws)
134
+ end
135
+
136
+ rule ws
137
+ [ \n\r\t]+
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,98 @@
1
+ require 'optparse'
2
+ require 'fig/package'
3
+
4
+ module Fig
5
+ def parse_descriptor(descriptor)
6
+ # todo should use treetop for these:
7
+ package_name = descriptor =~ /^([^:\/]+)/ ? $1 : nil
8
+ config_name = descriptor =~ /:([^:\/]+)/ ? $1 : nil
9
+ version_name = descriptor =~ /\/([^:\/]+)/ ? $1 : nil
10
+ return package_name, config_name, version_name
11
+ end
12
+
13
+ def parse_options(argv)
14
+ options = {}
15
+
16
+ parser = OptionParser.new do |opts|
17
+ opts.banner = "Usage: fig [--debug] [--update] [--config <config>] [--get <var> | --list | <package> | - <command>]"
18
+
19
+ opts.on('-?', '-h','--help','display this help text') do
20
+ puts opts
21
+ exit 1
22
+ end
23
+
24
+ options[:debug] = false
25
+ opts.on('-d', '--debug', 'print debug info') { options[:debug] = true }
26
+
27
+ options[:update] = false
28
+ opts.on('-u', '--update', 'check remote repository for updates') { options[:update] = true; options[:retrieve] = true }
29
+
30
+ options[:update_if_missing] = false
31
+ opts.on('-m', '--update-if-missing', 'check for updates only if package is missing locally') { options[:update_if_missing] = true; options[:retrieve] = true }
32
+
33
+ options[:config] = "default"
34
+ opts.on('-c', '--config CFG', 'name of configuration to apply') { |config| options[:config] = config }
35
+
36
+ options[:echo] = nil
37
+ opts.on('-g', '--get VAR', 'print value of environment variable') { |echo| options[:echo] = echo }
38
+
39
+ options[:publish] = nil
40
+ opts.on('--publish PKG', 'install package in local and remote repositories') { |publish| options[:publish] = publish }
41
+
42
+ options[:publish_local] = nil
43
+ opts.on('--publish-local PKG', 'install package in local repositorie only') { |publish_local| options[:publish_local] = publish_local }
44
+
45
+ options[:force] = nil
46
+ opts.on('--force', 'force overwriting of an existing remote package version') { |force| options[:force] = force }
47
+
48
+ options[:resources] =[]
49
+ opts.on('--resource PATH', 'resource to include in package (when using --publish)') do |path|
50
+ options[:resources] << Resource.new(path)
51
+ end
52
+
53
+ options[:archives] =[]
54
+ opts.on('--archive PATH', 'archive to include in package (when using --publish)') do |path|
55
+ options[:archives] << Archive.new(path)
56
+ end
57
+
58
+ options[:list] = false
59
+ opts.on('--list', 'list packages in local repository') { options[:list] = true }
60
+
61
+ options[:list_remote] = false
62
+ opts.on('--list-remote', 'list packages in remote repository') { options[:list_remote] = true }
63
+
64
+ options[:list_configs] = []
65
+ opts.on('--list-configs PKG', 'list configurations in package') { |descriptor| options[:list_configs] << descriptor }
66
+
67
+ options[:cleans] = []
68
+ opts.on('--clean PKG', 'remove package from local repository') { |descriptor| options[:cleans] << descriptor }
69
+
70
+ options[:modifiers] = []
71
+
72
+ opts.on('-i', '--include PKG', 'include package in environment') do |descriptor|
73
+ package_name, config_name, version_name = parse_descriptor(descriptor)
74
+ options[:modifiers] << Include.new(package_name, config_name, version_name)
75
+ end
76
+
77
+ opts.on('-s', '--set VAR=VAL', 'set environment variable') do |var_val|
78
+ var, val = var_val.split('=')
79
+ options[:modifiers] << Set.new(var, val)
80
+ end
81
+
82
+ opts.on('-p', '--append VAR=VAL', 'append environment variable') do |var_val|
83
+ var, val = var_val.split('=')
84
+ options[:modifiers] << Path.new(var, val)
85
+ end
86
+
87
+ options[:input] = nil
88
+ opts.on('--file FILE', 'fig file to read (use - for stdin)') { |path| options[:input] = path }
89
+ opts.on('--no-file', 'ignore package.fig file in current directory') { |path| options[:input] = :none }
90
+
91
+ options[:home] = ENV['FIG_HOME'] || File.expand_path("~/.fighome")
92
+ end
93
+
94
+ parser.parse!(argv)
95
+
96
+ return options, argv
97
+ end
98
+ end
data/lib/fig/os.rb ADDED
@@ -0,0 +1,325 @@
1
+ require 'fileutils'
2
+ # Must specify absolute path of ::Archive when using
3
+ # this module to avoid conflicts with Fig::Package::Archive
4
+ require 'libarchive_ruby' unless RUBY_PLATFORM == 'java'
5
+ require 'uri'
6
+ require 'net/http'
7
+ require 'net/ssh'
8
+ require 'net/sftp'
9
+ require 'tempfile'
10
+
11
+ module Fig
12
+ class NotFoundException < Exception
13
+ end
14
+
15
+ class OS
16
+ def list(dir)
17
+ Dir.entries(dir) - ['.','..']
18
+ end
19
+
20
+ def exist?(path)
21
+ File.exist?(path)
22
+ end
23
+
24
+ def mtime(path)
25
+ File.mtime(path)
26
+ end
27
+
28
+ def read(path)
29
+ File.read(path)
30
+ end
31
+
32
+ def write(path, content)
33
+ File.open(path, "wb") { |f| f.binmode; f << content }
34
+ end
35
+
36
+ SUCCESS = 0
37
+ NOT_MODIFIED = 3
38
+ NOT_FOUND = 4
39
+
40
+ def download_list(url)
41
+ begin
42
+ uri = URI.parse(url)
43
+ rescue
44
+ puts "Unable to parse url: '#{url}'"
45
+ exit 10
46
+ end
47
+ case uri.scheme
48
+ when "ftp"
49
+ ftp = Net::FTP.new(uri.host)
50
+ ftp.login
51
+ ftp.chdir(uri.path)
52
+ packages = []
53
+ ftp.retrlines('LIST -R .') do |line|
54
+ parts = line.gsub(/\\/, '/').sub(/^\.\//, '').sub(/:$/, '').split('/')
55
+ packages << parts.join('/') if parts.size == 2
56
+ end
57
+ ftp.close
58
+ packages
59
+ when "ssh"
60
+ packages = []
61
+ Net::SSH.start(uri.host, uri.user) do |ssh|
62
+ ls = ssh.exec!("[ -d #{uri.path} ] && find #{uri.path}")
63
+ if not ls.nil?
64
+ ls = ls.gsub(uri.path + "/", "").gsub(uri.path, "")
65
+ ls.each do |line|
66
+ parts = line.gsub(/\\/, '/').sub(/^\.\//, '').sub(/:$/, '').chomp().split('/')
67
+ packages << parts.join('/') if parts.size == 2
68
+ end
69
+ end
70
+ end
71
+ packages
72
+ else
73
+ puts "Protocol not supported: #{url}"
74
+ exit 10
75
+ end
76
+ end
77
+
78
+ def download(url, path)
79
+ FileUtils.mkdir_p(File.dirname(path))
80
+ uri = URI.parse(url)
81
+ case uri.scheme
82
+ when "ftp"
83
+ ftp = Net::FTP.new(uri.host)
84
+ ftp.login
85
+ begin
86
+ if File.exist?(path) && ftp.mtime(uri.path) <= File.mtime(path)
87
+ return false
88
+ else
89
+ puts "downloading #{url}"
90
+ ftp.getbinaryfile(uri.path, path, 256*1024)
91
+ return true
92
+ end
93
+ rescue Net::FTPPermError
94
+ raise NotFoundException.new
95
+ end
96
+ when "http"
97
+ http = Net::HTTP.new(uri.host)
98
+ puts "downloading #{url}"
99
+ File.open(path, "wb") do |file|
100
+ file.binmode
101
+ http.get(uri.path) do |block|
102
+ file.write(block)
103
+ end
104
+ end
105
+ when "ssh"
106
+ # TODO need better way to do conditional download
107
+ # timestamp = `ssh #{uri.user + '@' if uri.user}#{uri.host} "ruby -e 'puts File.mtime(\\"#{uri.path}\\").to_i'"`.to_i
108
+ timestamp = File.exist?(path) ? File.mtime(path).to_i : 0
109
+ cmd = `which fig-download`.strip + " #{timestamp} #{uri.path}"
110
+ ssh_download(uri.user, uri.host, path, cmd)
111
+ else
112
+ puts "Unknown protocol: #{url}"
113
+ exit 10
114
+ end
115
+ end
116
+
117
+ def download_resource(url, dir)
118
+ FileUtils.mkdir_p(dir)
119
+ download(url, File.join(dir, URI.parse(url).path.split('/').last))
120
+ end
121
+
122
+ def download_archive(url, dir)
123
+ FileUtils.mkdir_p(dir)
124
+ basename = URI.parse(url).path.split('/').last
125
+ path = File.join(dir, basename)
126
+ download(url, path)
127
+ case basename
128
+ when /\.tar\.gz$/
129
+ unpack_archive(dir, path)
130
+ when /\.tgz$/
131
+ unpack_archive(dir, path)
132
+ when /\.tar\.bz2$/
133
+ unpack_archive(dir, path)
134
+ when /\.zip$/
135
+ unpack_archive(dir, path)
136
+ else
137
+ puts "Unknown archive type: #{basename}"
138
+ exit 10
139
+ end
140
+ end
141
+
142
+ def upload(local_file, remote_file, user)
143
+ puts "uploading #{local_file} to #{remote_file}"
144
+ uri = URI.parse(remote_file)
145
+ case uri.scheme
146
+ when "ssh"
147
+ ssh_upload(uri.user, uri.host, local_file, remote_file)
148
+ when "ftp"
149
+ # fail unless system "curl -T #{local_file} --create-dirs --ftp-create-dirs #{remote_file}"
150
+ require 'net/ftp'
151
+ ftp_uri = URI.parse(ENV["FIG_REMOTE_URL"])
152
+ ftp_root_path = ftp_uri.path
153
+ ftp_root_dirs = ftp_uri.path.split("/")
154
+ remote_publish_path = uri.path[0, uri.path.rindex("/")]
155
+ remote_publish_dirs = remote_publish_path.split("/")
156
+ # Use array subtraction to deduce which project/version folder to upload to,
157
+ # i.e. [1,2,3] - [2,3,4] = [1]
158
+ remote_project_dirs = remote_publish_dirs - ftp_root_dirs
159
+ Net::FTP.open(uri.host) do |ftp|
160
+ ftp.login
161
+ # Assume that the FIG_REMOTE_URL path exists.
162
+ ftp.chdir(ftp_root_path)
163
+ remote_project_dirs.each do |dir|
164
+ # Can't automatically create parent directories, so do it manually.
165
+ if ftp.nlst().index(dir).nil?
166
+ ftp.mkdir(dir)
167
+ ftp.chdir(dir)
168
+ else
169
+ ftp.chdir(dir)
170
+ end
171
+ end
172
+ ftp.putbinaryfile(local_file)
173
+ end
174
+ end
175
+ end
176
+
177
+ def clear_directory(dir)
178
+ FileUtils.rm_rf(dir)
179
+ FileUtils.mkdir_p(dir)
180
+ end
181
+
182
+ def exec(dir,command)
183
+ Dir.chdir(dir) {
184
+ unless system command
185
+ puts "Command failed"
186
+ exit 10
187
+ end
188
+ }
189
+ end
190
+
191
+ def copy(source, target)
192
+ FileUtils.mkdir_p(File.dirname(target))
193
+ FileUtils.cp_r(source, target)
194
+ end
195
+
196
+ def move_file(dir, from, to)
197
+ Dir.chdir(dir) { FileUtils.mv(from, to, :force => true) }
198
+ end
199
+
200
+ def log_info(msg)
201
+ $stderr.puts msg
202
+ end
203
+
204
+ # Expects files_to_archive as an Array of filenames.
205
+ def create_archive(archive_name, files_to_archive)
206
+ if OS.java?
207
+ `tar czvf #{archive_name} #{files_to_archive.join(' ')}`
208
+ else
209
+ # TODO: Need to verify files_to_archive exists.
210
+ ::Archive.write_open_filename(archive_name, ::Archive::COMPRESSION_GZIP, ::Archive::FORMAT_TAR) do |ar|
211
+ files_to_archive.each do |fn|
212
+ ar.new_entry do |entry|
213
+ entry.copy_stat(fn)
214
+ entry.pathname = fn
215
+ ar.write_header(entry)
216
+ if !entry.directory?
217
+ ar.write_data(open(fn) {|f| f.binmode; f.read })
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ # This method can handle the following archive types:
226
+ # .tar.bz2
227
+ # .tar.gz
228
+ # .tgz
229
+ # .zip
230
+ def unpack_archive(dir, file)
231
+ Dir.chdir(dir) do
232
+ if OS.java?
233
+ `tar xzvf #{file}`
234
+ else
235
+ ::Archive.read_open_filename(file) do |ar|
236
+ while entry = ar.next_header
237
+ ar.extract(entry)
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ def self.windows?
245
+ Config::CONFIG['host_os'] =~ /mswin|mingw/
246
+ end
247
+
248
+ def self.java?
249
+ RUBY_PLATFORM == 'java'
250
+ end
251
+
252
+ def self.unix?
253
+ !windows?
254
+ end
255
+
256
+ def shell_exec(cmd)
257
+ if OS.windows?
258
+ Windows.shell_exec_windows(cmd)
259
+ else
260
+ shell_exec_unix(cmd)
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def shell_exec_unix(cmd)
267
+ Kernel.exec(ENV['SHELL'], '-c', cmd.join(' '))
268
+ end
269
+
270
+ def shell_exec_windows(cmd)
271
+ #command = ["C:/WINDOWS/system32/cmd.exe", "/C", "call"] + cmd
272
+ command = ["cmd.exe", "/C"] + cmd
273
+ command = command.join(' ')
274
+ Kernel.exec(command)
275
+ end
276
+
277
+ # path = The local path the file should be downloaded to.
278
+ # cmd = The command to be run on the remote host.
279
+ def ssh_download(user, host, path, cmd)
280
+ return_code = nil
281
+ tempfile = Tempfile.new("tmp")
282
+ Net::SSH.start(host, user) do |ssh|
283
+ ssh.open_channel do |channel|
284
+ channel.exec(cmd)
285
+ channel.on_data() { |ch, data| tempfile << data }
286
+ channel.on_extended_data() { |ch, type, data| $stderr.puts "SSH Download ERROR: #{data}" }
287
+ channel.on_request("exit-status") { |ch, request|
288
+ return_code = request.read_long
289
+ }
290
+ end
291
+ end
292
+
293
+ tempfile.close()
294
+
295
+ case return_code
296
+ when NOT_MODIFIED
297
+ tempfile.delete
298
+ return false
299
+ when NOT_FOUND
300
+ tempfile.delete
301
+ puts "File not found: #{path}"
302
+ exit 10
303
+ when SUCCESS
304
+ FileUtils.mv(tempfile.path, path)
305
+ return true
306
+ else
307
+ tempfile.delete
308
+ $stderr.puts "Unable to download file: #{return_code}"
309
+ exit 1
310
+ end
311
+ end
312
+
313
+ def ssh_upload(user, host, local_file, remote_file)
314
+ uri = URI.parse(remote_file)
315
+ dir = uri.path[0, uri.path.rindex('/')]
316
+ Net::SSH.start(host, user) do |ssh|
317
+ ssh.exec!("mkdir -p #{dir}")
318
+ end
319
+ Net::SFTP.start(host, user) do |sftp|
320
+ sftp.upload!(local_file, uri.path)
321
+ end
322
+ end
323
+
324
+ end
325
+ end