fig 0.1.67 → 0.1.69
Sign up to get free protection for your applications and to get access to all the features.
- data/Changes +63 -1
- data/lib/fig.rb +1 -1
- data/lib/fig/command.rb +59 -24
- data/lib/fig/command/action.rb +7 -0
- data/lib/fig/command/action/publish.rb +6 -2
- data/lib/fig/command/action/publish_local.rb +5 -1
- data/lib/fig/command/action/role/publish.rb +34 -13
- data/lib/fig/command/action/version.rb +5 -1
- data/lib/fig/command/options.rb +153 -261
- data/lib/fig/command/options/parser.rb +187 -0
- data/lib/fig/logging.rb +2 -2
- data/lib/fig/operating_system.rb +1 -1
- data/lib/fig/package.rb +6 -6
- data/lib/fig/parser.rb +4 -3
- data/lib/fig/repository.rb +33 -247
- data/lib/fig/repository_package_publisher.rb +318 -0
- data/lib/fig/update_lock.rb +68 -0
- data/lib/fig/working_directory_maintainer.rb +66 -32
- metadata +29 -26
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'set'
|
3
|
+
require 'socket'
|
4
|
+
require 'sys/admin'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
require 'fig'
|
8
|
+
require 'fig/at_exit'
|
9
|
+
require 'fig/logging'
|
10
|
+
require 'fig/not_found_error'
|
11
|
+
require 'fig/package_cache'
|
12
|
+
require 'fig/package_descriptor'
|
13
|
+
require 'fig/parser'
|
14
|
+
require 'fig/repository'
|
15
|
+
require 'fig/repository_error'
|
16
|
+
require 'fig/statement/archive'
|
17
|
+
require 'fig/statement/resource'
|
18
|
+
|
19
|
+
module Fig; end
|
20
|
+
|
21
|
+
class Fig::RepositoryPackagePublisher
|
22
|
+
attr_accessor :operating_system
|
23
|
+
attr_accessor :publish_listeners
|
24
|
+
attr_accessor :package_statements
|
25
|
+
attr_accessor :descriptor
|
26
|
+
attr_accessor :source_package
|
27
|
+
attr_accessor :was_forced
|
28
|
+
attr_accessor :base_temp_dir
|
29
|
+
attr_accessor :local_dir_for_package
|
30
|
+
attr_accessor :remote_dir_for_package
|
31
|
+
attr_accessor :local_fig_file_for_package
|
32
|
+
attr_accessor :remote_fig_file_for_package
|
33
|
+
attr_accessor :local_only
|
34
|
+
|
35
|
+
def publish_package()
|
36
|
+
derive_publish_metadata()
|
37
|
+
validate_asset_names()
|
38
|
+
|
39
|
+
temp_dir = publish_temp_dir()
|
40
|
+
@operating_system.delete_and_recreate_directory(temp_dir)
|
41
|
+
@operating_system.delete_and_recreate_directory(@local_dir_for_package)
|
42
|
+
|
43
|
+
fig_file = File.join(temp_dir, Fig::Repository::PACKAGE_FILE_IN_REPO)
|
44
|
+
content = publish_package_content_and_derive_definition_file()
|
45
|
+
@operating_system.write(fig_file, content)
|
46
|
+
|
47
|
+
if not @local_only
|
48
|
+
@operating_system.upload(fig_file, remote_fig_file_for_package())
|
49
|
+
end
|
50
|
+
@operating_system.copy(fig_file, local_fig_file_for_package())
|
51
|
+
|
52
|
+
notify_listeners
|
53
|
+
|
54
|
+
FileUtils.rm_rf(temp_dir)
|
55
|
+
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def validate_asset_names()
|
62
|
+
asset_statements = @package_statements.select { |s| s.is_asset? }
|
63
|
+
|
64
|
+
asset_names = Set.new()
|
65
|
+
asset_statements.each do
|
66
|
+
|statement|
|
67
|
+
|
68
|
+
asset_name = statement.asset_name()
|
69
|
+
if not asset_name.nil?
|
70
|
+
if asset_name == Fig::Repository::RESOURCES_FILE
|
71
|
+
Fig::Logging.fatal \
|
72
|
+
%Q<You cannot have an asset with the name "#{Fig::Repository::RESOURCES_FILE}"#{statement.position_string()} due to Fig implementation details.>
|
73
|
+
end
|
74
|
+
|
75
|
+
if asset_names.include?(asset_name)
|
76
|
+
Fig::Logging.fatal \
|
77
|
+
%Q<Found multiple archives with the name "#{asset_name}"#{statement.position_string()}. If these were allowed, archives would overwrite each other.>
|
78
|
+
raise Fig::RepositoryError.new
|
79
|
+
else
|
80
|
+
asset_names.add(asset_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def derive_publish_metadata()
|
87
|
+
@publish_time = Time.now()
|
88
|
+
@publish_login = Sys::Admin.get_login()
|
89
|
+
@publish_host = Socket.gethostname()
|
90
|
+
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
def publish_package_content_and_derive_definition_file()
|
95
|
+
@definition_file_lines = []
|
96
|
+
|
97
|
+
add_package_metadata_comments()
|
98
|
+
publish_package_content()
|
99
|
+
add_unparsed_text()
|
100
|
+
|
101
|
+
@definition_file_lines.flatten!
|
102
|
+
file_content = @definition_file_lines.join("\n")
|
103
|
+
file_content.gsub!(/\n{3,}/, "\n\n")
|
104
|
+
|
105
|
+
return file_content.strip() + "\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_package_metadata_comments()
|
109
|
+
@definition_file_lines <<
|
110
|
+
%Q<# Publishing information for #{@descriptor.to_string()}:>
|
111
|
+
@definition_file_lines << %q<#>
|
112
|
+
|
113
|
+
@definition_file_lines <<
|
114
|
+
%Q<# Time: #{@publish_time} (epoch: #{@publish_time.to_i()})>
|
115
|
+
|
116
|
+
@definition_file_lines << %Q<# User: #{@publish_login}>
|
117
|
+
@definition_file_lines << %Q<# Host: #{@publish_host}>
|
118
|
+
@definition_file_lines << %Q<# Args: "#{ARGV.join %q[", "]}">
|
119
|
+
@definition_file_lines << %Q<# Fig: v#{Fig::VERSION}>
|
120
|
+
@definition_file_lines << %q<#>
|
121
|
+
|
122
|
+
asset_statements =
|
123
|
+
@package_statements.select { |statement| statement.is_asset? }
|
124
|
+
asset_strings =
|
125
|
+
asset_statements.collect { |statement| statement.unparse('# ') }
|
126
|
+
|
127
|
+
if asset_strings.empty?
|
128
|
+
@definition_file_lines <<
|
129
|
+
%q<# There were no asset statements in the unpublished package definition.>
|
130
|
+
else
|
131
|
+
@definition_file_lines << %q<# Original asset statements: >
|
132
|
+
@definition_file_lines << %q<#>
|
133
|
+
@definition_file_lines << asset_strings
|
134
|
+
end
|
135
|
+
|
136
|
+
@definition_file_lines << %Q<\n>
|
137
|
+
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
# Deals with Archive and Resource statements. It downloads any remote
|
142
|
+
# files (those where the statement references a URL as opposed to a local
|
143
|
+
# file) and then copies all files into the local repository and the remote
|
144
|
+
# repository (if not a local-only publish).
|
145
|
+
def publish_package_content()
|
146
|
+
initialize_statements_to_publish()
|
147
|
+
create_resource_archive()
|
148
|
+
|
149
|
+
@statements_to_publish.each do
|
150
|
+
|statement|
|
151
|
+
|
152
|
+
if statement.is_asset?
|
153
|
+
publish_asset(statement)
|
154
|
+
else
|
155
|
+
@definition_file_lines << statement.unparse('')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
return
|
160
|
+
end
|
161
|
+
|
162
|
+
def initialize_statements_to_publish()
|
163
|
+
@resource_paths = []
|
164
|
+
|
165
|
+
@statements_to_publish = @package_statements.reject do |statement|
|
166
|
+
if (
|
167
|
+
statement.is_a?(Fig::Statement::Resource) &&
|
168
|
+
! Fig::Repository.is_url?(statement.url)
|
169
|
+
)
|
170
|
+
@resource_paths << statement.url
|
171
|
+
true
|
172
|
+
else
|
173
|
+
false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
return
|
178
|
+
end
|
179
|
+
|
180
|
+
def create_resource_archive()
|
181
|
+
if @resource_paths.size > 0
|
182
|
+
asset_paths = expand_globs_from(@resource_paths)
|
183
|
+
check_asset_paths(asset_paths)
|
184
|
+
|
185
|
+
file = Fig::Repository::RESOURCES_FILE
|
186
|
+
@operating_system.create_archive(file, asset_paths)
|
187
|
+
Fig::AtExit.add { File.delete(file) }
|
188
|
+
|
189
|
+
@statements_to_publish.unshift(
|
190
|
+
Fig::Statement::Archive.new(nil, nil, file)
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
return
|
195
|
+
end
|
196
|
+
|
197
|
+
def publish_asset(asset_statement)
|
198
|
+
asset_name = asset_statement.asset_name()
|
199
|
+
asset_remote = "#{remote_dir_for_package()}/#{asset_name}"
|
200
|
+
|
201
|
+
if Fig::Repository.is_url?(asset_statement.url)
|
202
|
+
asset_local = File.join(publish_temp_dir(), asset_name)
|
203
|
+
|
204
|
+
begin
|
205
|
+
@operating_system.download(asset_statement.url, asset_local)
|
206
|
+
rescue Fig::NotFoundError
|
207
|
+
Fig::Logging.fatal "Could not download #{asset_statement.url}."
|
208
|
+
raise Fig::RepositoryError.new
|
209
|
+
end
|
210
|
+
else
|
211
|
+
asset_local = asset_statement.url
|
212
|
+
check_asset_path(asset_local)
|
213
|
+
end
|
214
|
+
|
215
|
+
if not @local_only
|
216
|
+
@operating_system.upload(
|
217
|
+
asset_local, asset_remote
|
218
|
+
)
|
219
|
+
end
|
220
|
+
|
221
|
+
@operating_system.copy(
|
222
|
+
asset_local, @local_dir_for_package + '/' + asset_name
|
223
|
+
)
|
224
|
+
if asset_statement.is_a?(Fig::Statement::Archive)
|
225
|
+
@operating_system.unpack_archive(@local_dir_for_package, asset_name)
|
226
|
+
end
|
227
|
+
|
228
|
+
@definition_file_lines <<
|
229
|
+
asset_statement.class.new(nil, nil, asset_name).unparse('')
|
230
|
+
|
231
|
+
return
|
232
|
+
end
|
233
|
+
|
234
|
+
def add_unparsed_text()
|
235
|
+
if @source_package && @source_package.unparsed_text
|
236
|
+
|
237
|
+
@definition_file_lines << ''
|
238
|
+
@definition_file_lines << '# Original, unparsed package text:'
|
239
|
+
@definition_file_lines << '# '
|
240
|
+
@definition_file_lines << @source_package.unparsed_text.gsub(/^/, '# ')
|
241
|
+
end
|
242
|
+
|
243
|
+
return
|
244
|
+
end
|
245
|
+
|
246
|
+
def notify_listeners()
|
247
|
+
publish_information = {}
|
248
|
+
publish_information[:descriptor] = @descriptor
|
249
|
+
publish_information[:time] = @publish_time
|
250
|
+
publish_information[:login] = @publish_login
|
251
|
+
publish_information[:host] = @publish_host
|
252
|
+
|
253
|
+
# Ensure that we've really got booleans and not merely true or false
|
254
|
+
# values.
|
255
|
+
publish_information[:was_forced] = @was_forced ? true : false
|
256
|
+
publish_information[:local_only] = @local_only ? true : false
|
257
|
+
|
258
|
+
publish_information[:local_destination] = @local_dir_for_package
|
259
|
+
publish_information[:remote_destination] = @remote_dir_for_package
|
260
|
+
|
261
|
+
@publish_listeners.each do
|
262
|
+
|listener|
|
263
|
+
|
264
|
+
listener.published(publish_information)
|
265
|
+
end
|
266
|
+
|
267
|
+
return
|
268
|
+
end
|
269
|
+
|
270
|
+
def publish_temp_dir()
|
271
|
+
File.join(base_temp_dir(), 'publish')
|
272
|
+
end
|
273
|
+
|
274
|
+
def check_asset_path(asset_path)
|
275
|
+
if not File.exist?(asset_path)
|
276
|
+
Fig::Logging.fatal "Could not find file #{asset_path}."
|
277
|
+
raise Fig::RepositoryError.new
|
278
|
+
end
|
279
|
+
|
280
|
+
return
|
281
|
+
end
|
282
|
+
|
283
|
+
def check_asset_paths(asset_paths)
|
284
|
+
non_existing_paths =
|
285
|
+
asset_paths.select {|path| ! File.exist?(path) && ! File.symlink?(path) }
|
286
|
+
|
287
|
+
if not non_existing_paths.empty?
|
288
|
+
if non_existing_paths.size > 1
|
289
|
+
Fig::Logging.fatal "Could not find files: #{ non_existing_paths.join(', ') }"
|
290
|
+
else
|
291
|
+
Fig::Logging.fatal "Could not find file #{non_existing_paths[0]}."
|
292
|
+
end
|
293
|
+
|
294
|
+
raise Fig::RepositoryError.new
|
295
|
+
end
|
296
|
+
|
297
|
+
return
|
298
|
+
end
|
299
|
+
|
300
|
+
# 'resources' is an Array of fileglob patterns: ['tmp/foo/file1',
|
301
|
+
# 'tmp/foo/*.jar']
|
302
|
+
def expand_globs_from(resources)
|
303
|
+
expanded_files = []
|
304
|
+
|
305
|
+
resources.each do
|
306
|
+
|path|
|
307
|
+
|
308
|
+
globbed_files = Dir.glob(path)
|
309
|
+
if globbed_files.empty?
|
310
|
+
expanded_files << path
|
311
|
+
else
|
312
|
+
expanded_files.concat(globbed_files)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
return expanded_files
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'fig/user_input_error'
|
4
|
+
|
5
|
+
module Fig; end
|
6
|
+
|
7
|
+
class Fig::UpdateLock
|
8
|
+
def initialize(lock_directory, response)
|
9
|
+
set_up_lock(lock_directory, response)
|
10
|
+
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
def close()
|
15
|
+
@lock.close
|
16
|
+
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_up_lock(lock_directory, response)
|
23
|
+
FileUtils.mkdir_p(lock_directory)
|
24
|
+
|
25
|
+
# Tried using the directory itself as the lock, but Windows is
|
26
|
+
# non-cooperative.
|
27
|
+
lock_file = lock_directory + '/lock'
|
28
|
+
|
29
|
+
# Yes, there's a race condition here, but with the way Windows file locking
|
30
|
+
# works, it's better than a boot to the head.
|
31
|
+
if ! File.exists? lock_file
|
32
|
+
created_file = File.new(lock_file, 'w')
|
33
|
+
created_file.close
|
34
|
+
end
|
35
|
+
|
36
|
+
@lock = File.new(lock_file)
|
37
|
+
|
38
|
+
# *sigh* Ruby 1.8 doesn't support close_on_exec(), but we'll still use it
|
39
|
+
# if we can as a better attempt at safety.
|
40
|
+
if @lock.respond_to? :close_on_exec=
|
41
|
+
@lock.close_on_exec = true
|
42
|
+
end
|
43
|
+
|
44
|
+
if response == :wait
|
45
|
+
@lock.flock(File::LOCK_EX)
|
46
|
+
else
|
47
|
+
if ! @lock.flock(File::LOCK_EX | File::LOCK_NB)
|
48
|
+
raise_lock_usage_error(lock_directory)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
def raise_lock_usage_error(lock_directory)
|
56
|
+
raise Fig::UserInputError.new(<<-END_MESSAGE)
|
57
|
+
Cannot update while another instance of Fig is updating #{lock_directory}.
|
58
|
+
|
59
|
+
You can tell Fig to wait for update with
|
60
|
+
|
61
|
+
fig --update --update-lock-response wait ...
|
62
|
+
|
63
|
+
or you can throw caution to the wind with
|
64
|
+
|
65
|
+
fig --update --update-lock-response ignore ...
|
66
|
+
END_MESSAGE
|
67
|
+
end
|
68
|
+
end
|
@@ -95,44 +95,78 @@ class Fig::WorkingDirectoryMaintainer
|
|
95
95
|
|
96
96
|
def copy(source, relpath)
|
97
97
|
target = File.join(@base_dir, relpath)
|
98
|
+
|
99
|
+
if source_and_target_are_same?(source, target)
|
100
|
+
# Actually happened: Retrieve and "set" both set to ".". Victim's current
|
101
|
+
# directory included a ".git" directory. Update was done and then later,
|
102
|
+
# an update with different dependencies. Fig proceeded to delete all
|
103
|
+
# files that had previously existed in the current directory, including
|
104
|
+
# out of the git repo. Whoops.
|
105
|
+
Fig::Logging.warn %Q<Skipping copying "#{source}" to itself.>
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
98
109
|
if File.directory?(source)
|
99
|
-
|
100
|
-
Fig::Logging.debug "Copying directory #{source} to #{target}."
|
101
|
-
Dir.foreach(source) do |child|
|
102
|
-
if child != '.' and child != '..'
|
103
|
-
source_file = File.join(source, child)
|
104
|
-
target_file = File.join(relpath, child)
|
105
|
-
copy(source_file, target_file)
|
106
|
-
end
|
107
|
-
end
|
110
|
+
copy_directory(source, relpath, target)
|
108
111
|
else
|
109
|
-
|
110
|
-
|
111
|
-
Fig::Logging.debug \
|
112
|
-
"Copying file from #{source} to #{target}."
|
113
|
-
else
|
114
|
-
Fig::Logging.info(
|
115
|
-
Fig::Logging::Colorizable.new(
|
116
|
-
"+ [#{formatted_meta()}] #{relpath}",
|
117
|
-
:green,
|
118
|
-
nil
|
119
|
-
)
|
120
|
-
)
|
121
|
-
end
|
122
|
-
FileUtils.mkdir_p(File.dirname(target))
|
112
|
+
copy_file(source, relpath, target)
|
113
|
+
end
|
123
114
|
|
124
|
-
|
125
|
-
|
126
|
-
preserve = File.exist?(source) && ! File.symlink?(source)
|
115
|
+
return
|
116
|
+
end
|
127
117
|
|
128
|
-
|
129
|
-
|
130
|
-
|
118
|
+
def source_and_target_are_same?(source, target)
|
119
|
+
# Ruby 1.8 doesn't have File.absolute_path(), so we have to fall back to
|
120
|
+
# .expand_path().
|
121
|
+
source_absolute = File.expand_path(source)
|
122
|
+
target_absolute = File.expand_path(target)
|
123
|
+
|
124
|
+
return source_absolute == target_absolute
|
125
|
+
end
|
126
|
+
|
127
|
+
def copy_directory(source, relpath, target)
|
128
|
+
FileUtils.mkdir_p(target)
|
129
|
+
Fig::Logging.debug "Copying directory #{source} to #{target}."
|
130
|
+
|
131
|
+
Dir.foreach(source) do |child|
|
132
|
+
if child != '.' and child != '..'
|
133
|
+
source_file = File.join(source, child)
|
134
|
+
target_file = File.join(relpath, child)
|
135
|
+
copy(source_file, target_file)
|
131
136
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
137
|
+
end
|
138
|
+
|
139
|
+
return
|
140
|
+
end
|
141
|
+
|
142
|
+
def copy_file(source, relpath, target)
|
143
|
+
if should_copy_file?(source, target)
|
144
|
+
if Fig::Logging.debug?
|
145
|
+
Fig::Logging.debug \
|
146
|
+
"Copying file from #{source} to #{target}."
|
147
|
+
else
|
148
|
+
Fig::Logging.info(
|
149
|
+
Fig::Logging::Colorizable.new(
|
150
|
+
"+ [#{formatted_meta()}] #{relpath}",
|
151
|
+
:green,
|
152
|
+
nil
|
153
|
+
)
|
154
|
+
)
|
135
155
|
end
|
156
|
+
FileUtils.mkdir_p(File.dirname(target))
|
157
|
+
|
158
|
+
# If the source is a dangling symlink, then there's no time, etc. to
|
159
|
+
# preserve.
|
160
|
+
preserve = File.exist?(source) && ! File.symlink?(source)
|
161
|
+
|
162
|
+
FileUtils.copy_entry(
|
163
|
+
source, target, preserve, false, :remove_destination
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
if @package_meta
|
168
|
+
@package_meta.add_file(relpath)
|
169
|
+
@package_meta.mark_as_retrieved()
|
136
170
|
end
|
137
171
|
|
138
172
|
return
|