fig 0.1.24-universal-darwin9.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.
- data/LICENSE +27 -0
- data/README.md +215 -0
- data/bin/fig +187 -0
- data/bin/fig-download +20 -0
- data/lib/fig.rb +0 -0
- data/lib/fig/environment.rb +171 -0
- data/lib/fig/grammar.treetop +141 -0
- data/lib/fig/options.rb +98 -0
- data/lib/fig/os.rb +325 -0
- data/lib/fig/package.rb +197 -0
- data/lib/fig/parser.rb +27 -0
- data/lib/fig/repository.rb +240 -0
- data/lib/fig/windows.rb +46 -0
- data/spec/fig_spec.rb +331 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/win_spec.rb +21 -0
- metadata +140 -0
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
|
data/lib/fig/options.rb
ADDED
@@ -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
|