tpkg 1.19.2 → 1.20.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -2
- data/bin/gem2tpkg +1 -1
- data/bin/tpkg +11 -2
- data/lib/tpkg.rb +241 -141
- data/lib/tpkg/deployer.rb +22 -48
- metadata +16 -16
data/Rakefile
CHANGED
@@ -4,8 +4,8 @@ spec = Gem::Specification.new do |s|
|
|
4
4
|
s.summary = 'tpkg Application Packaging & Deployment'
|
5
5
|
s.add_dependency('facter')
|
6
6
|
s.add_dependency('net-ssh')
|
7
|
-
s.add_dependency('kwalify')
|
8
|
-
s.version = '1.
|
7
|
+
s.add_dependency('ddao-kwalify')
|
8
|
+
s.version = '1.20.0'
|
9
9
|
s.authors = ['Darren Dao', 'Jason Heiss']
|
10
10
|
s.email = 'tpkg-users@lists.sourceforge.net'
|
11
11
|
s.homepage = 'http://tpkg.sourceforge.net'
|
data/bin/gem2tpkg
CHANGED
@@ -12,7 +12,7 @@ require 'facter'
|
|
12
12
|
|
13
13
|
# We don't want to just use the first gem command in the user's PATH by
|
14
14
|
# default, as that may not be a tpkg gem. I.e. /usr/bin/gem on Mac OS X.
|
15
|
-
DEFAULT_GEM_COMMAND = "#{Tpkg::DEFAULT_BASE}/ruby-
|
15
|
+
DEFAULT_GEM_COMMAND = "#{Tpkg::DEFAULT_BASE}/ruby-ypc/bin/gem"
|
16
16
|
|
17
17
|
# Haven't found a Ruby method for creating temporary directories,
|
18
18
|
# so create a temporary file and replace it with a directory.
|
data/bin/tpkg
CHANGED
@@ -30,11 +30,17 @@ require 'tpkg'
|
|
30
30
|
@rerun_with_sudo = false
|
31
31
|
@tpkg_options = {}
|
32
32
|
@init_options = {}
|
33
|
+
@compress = nil
|
34
|
+
|
33
35
|
|
34
36
|
def rerun_with_sudo_if_necessary
|
35
37
|
if Process.euid != 0 && @sudo
|
36
38
|
warn "Executing with sudo"
|
37
|
-
|
39
|
+
if ENV['TPKG_HOME']
|
40
|
+
exec('sudo', 'env', "TPKG_HOME=#{ENV['TPKG_HOME']}", $0, *ARGV)
|
41
|
+
else
|
42
|
+
exec('sudo', $0, *ARGV)
|
43
|
+
end
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
@@ -234,6 +240,9 @@ opts.on('--deploy-as', '=USERNAME', 'What username to use for deploying to remot
|
|
234
240
|
@deploy_options["deploy-as"] = opt
|
235
241
|
@deploy_params = @deploy_params - ['--deploy-as']
|
236
242
|
end
|
243
|
+
opts.on('--compress', '=[TYPE]', 'Compress files when making packages.') do |opt|
|
244
|
+
@compress = opt
|
245
|
+
end
|
237
246
|
opts.on('--debug', 'Print lots of messages about what tpkg is doing') do |opt|
|
238
247
|
@debug = opt
|
239
248
|
Tpkg::set_debug(@debug)
|
@@ -336,7 +345,7 @@ end
|
|
336
345
|
ret_val = 0
|
337
346
|
case @action
|
338
347
|
when :make
|
339
|
-
pkgfile = Tpkg::make_package(@action_value, passphrase_callback, {:force => @force})
|
348
|
+
pkgfile = Tpkg::make_package(@action_value, passphrase_callback, {:force => @force, :compress => @compress})
|
340
349
|
if pkgfile
|
341
350
|
puts "Package is #{pkgfile}"
|
342
351
|
else
|
data/lib/tpkg.rb
CHANGED
@@ -56,7 +56,7 @@ require 'kwalify' # for validating yaml
|
|
56
56
|
|
57
57
|
class Tpkg
|
58
58
|
|
59
|
-
VERSION = '1.
|
59
|
+
VERSION = '1.20.0'
|
60
60
|
CONFIGDIR = '/etc'
|
61
61
|
|
62
62
|
GENERIC_ERR = 1
|
@@ -86,7 +86,7 @@ class Tpkg
|
|
86
86
|
# Raises an exception if a suitable tar cannot be found
|
87
87
|
@@tar = nil
|
88
88
|
@@taroptions = ""
|
89
|
-
@@
|
89
|
+
@@tarinfo = {:version => 'unknown'}
|
90
90
|
TARNAMES = ['tar', 'gtar', 'gnutar', 'bsdtar']
|
91
91
|
def self.find_tar
|
92
92
|
if !@@tar
|
@@ -96,15 +96,17 @@ class Tpkg
|
|
96
96
|
if File.executable?(File.join(path, tarname))
|
97
97
|
IO.popen("#{File.join(path, tarname)} --version 2>/dev/null") do |pipe|
|
98
98
|
pipe.each_line do |line|
|
99
|
-
if line.include?('GNU tar')
|
100
|
-
@@
|
99
|
+
if line.include?('GNU tar')
|
100
|
+
@@tarinfo[:type] = 'gnu'
|
101
101
|
@@tar = File.join(path, tarname)
|
102
|
-
throw :tar_found
|
103
102
|
elsif line.include?('bsdtar')
|
104
|
-
@@
|
103
|
+
@@tarinfo[:type] = 'bsd'
|
105
104
|
@@tar = File.join(path, tarname)
|
106
|
-
throw :tar_found
|
107
105
|
end
|
106
|
+
if line =~ /(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)/
|
107
|
+
@@tarinfo[:version] = [$1, $2, $3].compact.join(".")
|
108
|
+
end
|
109
|
+
throw :tar_found if @@tar
|
108
110
|
end
|
109
111
|
end
|
110
112
|
end
|
@@ -120,7 +122,7 @@ class Tpkg
|
|
120
122
|
# gnu tar should just ignore them and gives a warning. This is what the latest gnu tar
|
121
123
|
# will do. However, on older gnu tar, it only threw an error at the end. The work
|
122
124
|
# around is to explicitly tell gnu tar to ignore those extensions.
|
123
|
-
if @@
|
125
|
+
if @@tarinfo[:type] == 'gnu' && @@tarinfo[:version] != 'unknown' && @@tarinfo[:version] >= '1.15.1'
|
124
126
|
@@taroptions = "--pax-option='delete=SCHILY.*'"
|
125
127
|
end
|
126
128
|
@@tar.dup
|
@@ -265,14 +267,18 @@ class Tpkg
|
|
265
267
|
metadata_text = File.read(metadata_file)
|
266
268
|
metadata = Metadata.new(metadata_text, metadata_format)
|
267
269
|
|
270
|
+
# This is for when we're in developement mode or when installed as gem
|
271
|
+
if File.exists?(File.join(File.dirname(File.dirname(__FILE__)), "schema"))
|
272
|
+
schema_dir = File.join(File.dirname(File.dirname(__FILE__)), "schema")
|
268
273
|
# This is the directory where we put our dtd/schema for validating
|
269
274
|
# the metadata file
|
270
|
-
|
275
|
+
elsif File.exist?(File.join(CONFIGDIR, 'tpkg', 'schema'))
|
271
276
|
schema_dir = File.join(CONFIGDIR, 'tpkg', 'schema')
|
272
|
-
else
|
273
|
-
|
277
|
+
else
|
278
|
+
warn "Warning: unable to find schema for tpkg.yml"
|
274
279
|
end
|
275
|
-
|
280
|
+
|
281
|
+
errors = metadata.validate(schema_dir) if schema_dir
|
276
282
|
if errors && !errors.empty?
|
277
283
|
puts "Bad metadata file. Possible error(s):"
|
278
284
|
errors.each {|e| puts e }
|
@@ -362,6 +368,11 @@ class Tpkg
|
|
362
368
|
csx.puts('</tpkg_checksums>')
|
363
369
|
end
|
364
370
|
|
371
|
+
# compress if needed
|
372
|
+
if options[:compress]
|
373
|
+
tpkgfile = compress_file(tpkgfile, options[:compress])
|
374
|
+
end
|
375
|
+
|
365
376
|
# Tar up checksum.xml and the main tarball
|
366
377
|
system("#{find_tar} -C #{workdir} -cf #{pkgfile} #{package_filename}") || raise("Final package creation failed")
|
367
378
|
ensure
|
@@ -508,7 +519,9 @@ class Tpkg
|
|
508
519
|
raise("Unrecognized checksum algorithm #{checksum.elements['algorithm']}")
|
509
520
|
end
|
510
521
|
# Extract tpkg.tar from the package and digest it
|
511
|
-
|
522
|
+
extract_tpkg_tar_command = cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
523
|
+
IO.popen(extract_tpkg_tar_command) do |pipe|
|
524
|
+
#IO.popen("#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar')} #{@@taroptions}") do |pipe|
|
512
525
|
# Package files can be quite large, so we digest the package in
|
513
526
|
# chunks. A survey of the Internet turns up someone who tested
|
514
527
|
# various chunk sizes on various platforms and found 4k to be
|
@@ -537,10 +550,13 @@ class Tpkg
|
|
537
550
|
['yml','xml'].each do |format|
|
538
551
|
file = File.join('tpkg', "tpkg.#{format}")
|
539
552
|
|
553
|
+
extract_tpkg_tar_command = cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
554
|
+
|
540
555
|
# use popen3 instead of popen because popen display stderr when there's an error such as
|
541
556
|
# tpkg.yml not being there, which is something we want to ignore since old tpkg doesn't
|
542
557
|
# have tpkg.yml file
|
543
|
-
stdin, stdout, stderr = Open3.popen3("#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar')} | #{find_tar} -xf - -O #{file}")
|
558
|
+
#stdin, stdout, stderr = Open3.popen3("#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar')} | #{find_tar} -xf - -O #{file}")
|
559
|
+
stdin, stdout, stderr = Open3.popen3("#{extract_tpkg_tar_command} | #{find_tar} -xf - -O #{file}")
|
544
560
|
filecontent = stdout.read
|
545
561
|
if filecontent.nil? or filecontent.empty?
|
546
562
|
next
|
@@ -566,7 +582,8 @@ class Tpkg
|
|
566
582
|
verify_package_checksum(package_file)
|
567
583
|
# Extract and parse tpkg.xml
|
568
584
|
tpkg_xml = nil
|
569
|
-
|
585
|
+
extract_tpkg_tar_command = cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
586
|
+
IO.popen("#{extract_tpkg_tar_command} | #{find_tar} -xf - -O #{File.join('tpkg', 'tpkg.xml')}") do |pipe|
|
570
587
|
tpkg_xml = REXML::Document.new(pipe.read)
|
571
588
|
end
|
572
589
|
if !$?.success?
|
@@ -671,7 +688,8 @@ class Tpkg
|
|
671
688
|
# And write that out to metadata.yml
|
672
689
|
metadata_tmpfile = Tempfile.new('metadata.yml', dest)
|
673
690
|
metadata.each do | metadata |
|
674
|
-
YAML::dump(metadata.to_hash, metadata_tmpfile)
|
691
|
+
YAML::dump(metadata.to_hash.recursively{|h| h.stringify_keys }, metadata_tmpfile)
|
692
|
+
#YAML::dump(metadata.to_hash, metadata_tmpfile)
|
675
693
|
end
|
676
694
|
metadata_tmpfile.close
|
677
695
|
File.chmod(0644, metadata_tmpfile.path)
|
@@ -718,6 +736,12 @@ class Tpkg
|
|
718
736
|
Facter['lsbmajdistrelease'].value &&
|
719
737
|
!Facter['lsbmajdistrelease'].value.empty?
|
720
738
|
osver = Facter['lsbmajdistrelease'].value
|
739
|
+
elsif operatingsystem == 'Ubuntu'
|
740
|
+
# Work around lack of lsbmajdistrelease on older versions of Ubuntu
|
741
|
+
# due to older version of facter. Support for lsbmajdistrelease on
|
742
|
+
# Ubuntu was added in facter 1.5.3, but there's no good way to force
|
743
|
+
# older Ubuntu systems to a newer version of facter.
|
744
|
+
osver = Facter['lsbdistrelease'].value.split('.').first
|
721
745
|
elsif Facter['kernel'] &&
|
722
746
|
Facter['kernel'].value == 'Darwin' &&
|
723
747
|
Facter['macosx_productversion'] &&
|
@@ -940,7 +964,8 @@ class Tpkg
|
|
940
964
|
files[:root] = []
|
941
965
|
files[:reloc] = []
|
942
966
|
topleveldir = package_toplevel_directory(package_file)
|
943
|
-
|
967
|
+
extract_tpkg_tar_cmd = cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
968
|
+
IO.popen("#{extract_tpkg_tar_cmd} | #{find_tar} #{@@taroptions} -tf -") do |pipe|
|
944
969
|
pipe.each do |file|
|
945
970
|
file.chomp!
|
946
971
|
if file =~ Regexp.new(File.join('tpkg', 'root'))
|
@@ -1104,7 +1129,8 @@ class Tpkg
|
|
1104
1129
|
begin
|
1105
1130
|
topleveldir = Tpkg::package_toplevel_directory(package_file)
|
1106
1131
|
workdir = Tpkg::tempdir(topleveldir)
|
1107
|
-
|
1132
|
+
extract_tpkg_tar_command = cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
1133
|
+
system("#{extract_tpkg_tar_command} | #{find_tar} #{@@taroptions} -C #{workdir} -xpf -")
|
1108
1134
|
|
1109
1135
|
if File.exist?(File.join(workdir,"tpkg", "tpkg.yml"))
|
1110
1136
|
metadata_file = File.join(workdir,"tpkg", "tpkg.yml")
|
@@ -1162,6 +1188,53 @@ class Tpkg
|
|
1162
1188
|
end
|
1163
1189
|
return perms, uid, gid
|
1164
1190
|
end
|
1191
|
+
|
1192
|
+
# Given a package file, figure out of tpkg.tar was compressed
|
1193
|
+
def self.get_compression(package_file)
|
1194
|
+
compression = nil
|
1195
|
+
IO.popen("#{find_tar} -tf #{package_file} #{@@taroptions}") do |pipe|
|
1196
|
+
pipe.each do |file|
|
1197
|
+
if file =~ /tpkg.tar.gz$/
|
1198
|
+
compression = "gzip"
|
1199
|
+
elsif file =~ /tpkg.tar.bz2$/
|
1200
|
+
compression = "bz2"
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
return compression
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
# Given a .tpkg file and the topleveldir, generate the command for
|
1208
|
+
# extracting tpkg.tar
|
1209
|
+
def self.cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
1210
|
+
compression = get_compression(package_file)
|
1211
|
+
if compression == "gzip"
|
1212
|
+
cmd = "#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar.gz')} #{@@taroptions} | gunzip -c"
|
1213
|
+
elsif compression == "bz2"
|
1214
|
+
cmd = "#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar.bz2')} #{@@taroptions} | bunzip2 -c"
|
1215
|
+
else
|
1216
|
+
cmd = "#{find_tar} -xf #{package_file} -O #{File.join(topleveldir, 'tpkg.tar')} #{@@taroptions}"
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
# Compresses the file using the compression type
|
1221
|
+
# specified by the compress flag
|
1222
|
+
# Returns the compressed file
|
1223
|
+
def self.compress_file(file, compress)
|
1224
|
+
if compress == true or compress == "gzip"
|
1225
|
+
result = "#{file}.gz"
|
1226
|
+
system("gzip #{file}")
|
1227
|
+
elsif compress == "bz2"
|
1228
|
+
result = "#{file}.bz2"
|
1229
|
+
system("bzip2 #{file}")
|
1230
|
+
else
|
1231
|
+
raise "Compression #{compress} is not supported"
|
1232
|
+
end
|
1233
|
+
if !$?.success? or !File.exists?(result)
|
1234
|
+
raise "Failed to compress the package"
|
1235
|
+
end
|
1236
|
+
return result
|
1237
|
+
end
|
1165
1238
|
|
1166
1239
|
#
|
1167
1240
|
# Instance methods
|
@@ -1512,7 +1585,11 @@ class Tpkg
|
|
1512
1585
|
else
|
1513
1586
|
version = debversion
|
1514
1587
|
end
|
1515
|
-
|
1588
|
+
# We want packages with a state of "installed". However,
|
1589
|
+
# there's also a state of "not-installed", and the state
|
1590
|
+
# field contains several space-seperated values, so we have
|
1591
|
+
# to be somewhat careful to pick out "installed".
|
1592
|
+
if status.split(' ').include?('installed')
|
1516
1593
|
pkg = pkg_for_native_package(name, version, package_version, :native_installed)
|
1517
1594
|
native_packages << pkg
|
1518
1595
|
end
|
@@ -2457,7 +2534,7 @@ class Tpkg
|
|
2457
2534
|
# permissions and ownership, etc. Does not check for conflicting
|
2458
2535
|
# files or packages, etc. Those checks (if desired) must be done before
|
2459
2536
|
# calling this method.
|
2460
|
-
def unpack(package_file,
|
2537
|
+
def unpack(package_file, options={})
|
2461
2538
|
ret_val = 0
|
2462
2539
|
metadata = Tpkg::metadata_from_package(package_file)
|
2463
2540
|
|
@@ -2470,7 +2547,8 @@ class Tpkg
|
|
2470
2547
|
# directory structure in the package.
|
2471
2548
|
topleveldir = Tpkg::package_toplevel_directory(package_file)
|
2472
2549
|
workdir = Tpkg::tempdir(topleveldir, @tmp_directory)
|
2473
|
-
|
2550
|
+
extract_tpkg_tar_cmd = Tpkg::cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
2551
|
+
system("#{extract_tpkg_tar_cmd} | #{@tar} #{@@taroptions} -C #{workdir} -xpf -")
|
2474
2552
|
files_info = {} # store perms, uid, gid, etc. for files
|
2475
2553
|
checksums_of_decrypted_files = {}
|
2476
2554
|
root_dir = File.join(workdir, 'tpkg', 'root')
|
@@ -2481,7 +2559,7 @@ class Tpkg
|
|
2481
2559
|
# Get list of conflicting files/directories & store their perm/ownership. That way, we can
|
2482
2560
|
# set them to the correct values later on in order to preserve them.
|
2483
2561
|
# TODO: verify this command works on all platforms
|
2484
|
-
files = `#{
|
2562
|
+
files = `#{extract_tpkg_tar_cmd} | #{@tar} #{@@taroptions} -tf -`
|
2485
2563
|
files = files.split("\n")
|
2486
2564
|
conflicting_files = {}
|
2487
2565
|
files.each do | file |
|
@@ -2495,59 +2573,9 @@ class Tpkg
|
|
2495
2573
|
end
|
2496
2574
|
end
|
2497
2575
|
|
2498
|
-
|
2499
|
-
if File.exist?(File.join(workdir, 'tpkg', 'preinstall'))
|
2500
|
-
pwd = Dir.pwd
|
2501
|
-
# chdir into the working directory so that the user can specify a
|
2502
|
-
# relative path to their file/script.
|
2503
|
-
Dir.chdir(File.join(workdir, 'tpkg'))
|
2504
|
-
|
2505
|
-
# Warn the user about non-executable files, as system will just
|
2506
|
-
# silently fail and exit if that's the case.
|
2507
|
-
if !File.executable?(File.join(workdir, 'tpkg', 'preinstall'))
|
2508
|
-
warn "Warning: preinstall script for #{File.basename(package_file)} is not executable, execution will likely fail"
|
2509
|
-
end
|
2510
|
-
if @force
|
2511
|
-
system(File.join(workdir, 'tpkg', 'preinstall')) || warn("Warning: preinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
2512
|
-
else
|
2513
|
-
system(File.join(workdir, 'tpkg', 'preinstall')) || raise("Error: preinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
2514
|
-
end
|
2515
|
-
# Switch back to our previous directory
|
2516
|
-
Dir.chdir(pwd)
|
2517
|
-
end
|
2576
|
+
run_preinstall(package_file, workdir)
|
2518
2577
|
|
2519
|
-
|
2520
|
-
metadata[:externals].each do |external|
|
2521
|
-
# If the external references a datafile or datascript then read/run it
|
2522
|
-
# now that we've unpacked the package contents and have the file/script
|
2523
|
-
# available. This will get us the data for the external.
|
2524
|
-
if external[:datafile] || external[:datascript]
|
2525
|
-
pwd = Dir.pwd
|
2526
|
-
# chdir into the working directory so that the user can specify a
|
2527
|
-
# relative path to their file/script.
|
2528
|
-
Dir.chdir(File.join(workdir, 'tpkg'))
|
2529
|
-
if external[:datafile]
|
2530
|
-
# Read the file
|
2531
|
-
external[:data] = IO.read(external[:datafile])
|
2532
|
-
# Drop the datafile key so that we don't waste time re-reading the
|
2533
|
-
# datafile again in the future.
|
2534
|
-
external.delete(:datafile)
|
2535
|
-
elsif external[:datascript]
|
2536
|
-
# Run the script
|
2537
|
-
IO.popen(external[:datascript]) do |pipe|
|
2538
|
-
external[:data] = pipe.read
|
2539
|
-
end
|
2540
|
-
# Drop the datascript key so that we don't waste time re-running the
|
2541
|
-
# datascript again in the future.
|
2542
|
-
external.delete(:datascript)
|
2543
|
-
end
|
2544
|
-
# Switch back to our previous directory
|
2545
|
-
Dir.chdir(pwd)
|
2546
|
-
end
|
2547
|
-
if !options[:externals_to_skip] || !options[:externals_to_skip].include?(external)
|
2548
|
-
run_external(metadata[:filename], :install, external[:name], external[:data])
|
2549
|
-
end
|
2550
|
-
end if metadata[:externals]
|
2578
|
+
run_externals_for_install(metadata, workdir, options[:externals_to_skip])
|
2551
2579
|
|
2552
2580
|
# Since we're stuck with unpacking to a temporary folder take
|
2553
2581
|
# advantage of that to handle permissions, ownership and decryption
|
@@ -2665,7 +2693,7 @@ class Tpkg
|
|
2665
2693
|
|
2666
2694
|
# Decrypt any files marked for decryption
|
2667
2695
|
if tpkgfile[:encrypt]
|
2668
|
-
if passphrase
|
2696
|
+
if !options[:passphrase]
|
2669
2697
|
# If the user didn't supply a passphrase then just remove the
|
2670
2698
|
# encrypted file. This allows users to install packages that
|
2671
2699
|
# contain encrypted files for which they don't have the
|
@@ -2675,7 +2703,7 @@ class Tpkg
|
|
2675
2703
|
else
|
2676
2704
|
(1..3).each do | i |
|
2677
2705
|
begin
|
2678
|
-
Tpkg::decrypt(metadata[:name], working_path, passphrase)
|
2706
|
+
Tpkg::decrypt(metadata[:name], working_path, options[:passphrase])
|
2679
2707
|
break
|
2680
2708
|
rescue OpenSSL::CipherError
|
2681
2709
|
@@passphrase = nil
|
@@ -2686,8 +2714,7 @@ class Tpkg
|
|
2686
2714
|
end
|
2687
2715
|
end
|
2688
2716
|
end
|
2689
|
-
|
2690
|
-
#digest = Digest::SHA256.file(working_path).hexdigest
|
2717
|
+
|
2691
2718
|
digest = Digest::SHA256.hexdigest(File.read(working_path))
|
2692
2719
|
# get checksum for the decrypted file. Will be used for creating file_metadata.xml
|
2693
2720
|
checksums_of_decrypted_files[File.expand_path(tpkg_path)] = digest
|
@@ -2733,67 +2760,10 @@ class Tpkg
|
|
2733
2760
|
install_init_scripts(metadata)
|
2734
2761
|
install_crontabs(metadata)
|
2735
2762
|
|
2736
|
-
|
2737
|
-
|
2738
|
-
|
2739
|
-
# chdir into the working directory so that the user can specify a
|
2740
|
-
# relative path to their file/script.
|
2741
|
-
Dir.chdir(File.join(workdir, 'tpkg'))
|
2742
|
-
|
2743
|
-
# Warn the user about non-executable files, as system will just
|
2744
|
-
# silently fail and exit if that's the case.
|
2745
|
-
if !File.executable?(File.join(workdir, 'tpkg', 'postinstall'))
|
2746
|
-
warn "Warning: postinstall script for #{File.basename(package_file)} is not executable, execution will likely fail"
|
2747
|
-
end
|
2748
|
-
# Note this only warns the user if the postinstall fails, it does
|
2749
|
-
# not raise an exception like we do if preinstall fails. Raising
|
2750
|
-
# an exception would leave the package's files installed but the
|
2751
|
-
# package not registered as installed, which does not seem
|
2752
|
-
# desirable. We could remove the package's files and raise an
|
2753
|
-
# exception, but this seems the best approach to me.
|
2754
|
-
system(File.join(workdir, 'tpkg', 'postinstall')) || warn("Warning: postinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
2755
|
-
ret_val = POSTINSTALL_ERR if $?.exitstatus > 0
|
2756
|
-
|
2757
|
-
# Switch back to our previous directory
|
2758
|
-
Dir.chdir(pwd)
|
2759
|
-
end
|
2763
|
+
ret_val = run_postinstall(package_file, workdir)
|
2764
|
+
|
2765
|
+
save_package_metadata(package_file, workdir, metadata, files_info, checksums_of_decrypted_files)
|
2760
2766
|
|
2761
|
-
# Save metadata for this pkg
|
2762
|
-
package_name = File.basename(package_file, File.extname(package_file))
|
2763
|
-
package_metadata_dir = File.join(@metadata_directory, package_name)
|
2764
|
-
FileUtils.mkdir_p(package_metadata_dir)
|
2765
|
-
metadata_file = File.new(File.join(package_metadata_dir, "tpkg.yml"), "w")
|
2766
|
-
metadata.write(metadata_file)
|
2767
|
-
metadata_file.close
|
2768
|
-
|
2769
|
-
# Save file_metadata.yml for this pkg
|
2770
|
-
if File.exist?(File.join(workdir, 'tpkg', 'file_metadata.bin'))
|
2771
|
-
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.bin')), 'bin')
|
2772
|
-
elsif File.exist?(File.join(workdir, 'tpkg', 'file_metadata.yml'))
|
2773
|
-
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.yml')), 'yml')
|
2774
|
-
elsif File.exists?(File.join(workdir, 'tpkg', 'file_metadata.xml'))
|
2775
|
-
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.xml')), 'xml')
|
2776
|
-
end
|
2777
|
-
if file_metadata
|
2778
|
-
file_metadata[:package_file] = File.basename(package_file)
|
2779
|
-
file_metadata[:files].each do |file|
|
2780
|
-
acl = files_info[file[:path]]
|
2781
|
-
file.merge!(acl) unless acl.nil?
|
2782
|
-
digest = checksums_of_decrypted_files[File.expand_path(file[:path])]
|
2783
|
-
if digest
|
2784
|
-
digests = file[:checksum][:digests]
|
2785
|
-
digests[0][:encrypted] = true
|
2786
|
-
digests[1] = {:decrypted => true, :value => digest}
|
2787
|
-
end
|
2788
|
-
end
|
2789
|
-
|
2790
|
-
file = File.open(File.join(package_metadata_dir, "file_metadata.bin"), "w")
|
2791
|
-
Marshal.dump(file_metadata.to_hash, file)
|
2792
|
-
file.close
|
2793
|
-
else
|
2794
|
-
warn "Warning: package #{File.basename(package_file)} does not include file_metadata information."
|
2795
|
-
end
|
2796
|
-
|
2797
2767
|
# Copy the package file to the directory for installed packages
|
2798
2768
|
FileUtils.cp(package_file, @installed_directory)
|
2799
2769
|
|
@@ -3039,6 +3009,135 @@ class Tpkg
|
|
3039
3009
|
end
|
3040
3010
|
end
|
3041
3011
|
|
3012
|
+
def run_preinstall(package_file, workdir)
|
3013
|
+
if File.exist?(File.join(workdir, 'tpkg', 'preinstall'))
|
3014
|
+
pwd = Dir.pwd
|
3015
|
+
# chdir into the working directory so that the user can specify
|
3016
|
+
# relative paths to other files in the package.
|
3017
|
+
Dir.chdir(File.join(workdir, 'tpkg'))
|
3018
|
+
|
3019
|
+
begin
|
3020
|
+
# Warn the user about non-executable files, as system will just
|
3021
|
+
# silently fail and exit if that's the case.
|
3022
|
+
if !File.executable?(File.join(workdir, 'tpkg', 'preinstall'))
|
3023
|
+
warn "Warning: preinstall script for #{File.basename(package_file)} is not executable, execution will likely fail"
|
3024
|
+
end
|
3025
|
+
if @force
|
3026
|
+
system(File.join(workdir, 'tpkg', 'preinstall')) || warn("Warning: preinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
3027
|
+
else
|
3028
|
+
system(File.join(workdir, 'tpkg', 'preinstall')) || raise("Error: preinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
3029
|
+
end
|
3030
|
+
ensure
|
3031
|
+
# Switch back to our previous directory
|
3032
|
+
Dir.chdir(pwd)
|
3033
|
+
end
|
3034
|
+
end
|
3035
|
+
end
|
3036
|
+
def run_postinstall(package_file, workdir)
|
3037
|
+
r = 0
|
3038
|
+
if File.exist?(File.join(workdir, 'tpkg', 'postinstall'))
|
3039
|
+
pwd = Dir.pwd
|
3040
|
+
# chdir into the working directory so that the user can specify
|
3041
|
+
# relative paths to other files in the package.
|
3042
|
+
Dir.chdir(File.join(workdir, 'tpkg'))
|
3043
|
+
|
3044
|
+
begin
|
3045
|
+
# Warn the user about non-executable files, as system will just
|
3046
|
+
# silently fail and exit if that's the case.
|
3047
|
+
if !File.executable?(File.join(workdir, 'tpkg', 'postinstall'))
|
3048
|
+
warn "Warning: postinstall script for #{File.basename(package_file)} is not executable, execution will likely fail"
|
3049
|
+
end
|
3050
|
+
# Note this only warns the user if the postinstall fails, it does
|
3051
|
+
# not raise an exception like we do if preinstall fails. Raising
|
3052
|
+
# an exception would leave the package's files installed but the
|
3053
|
+
# package not registered as installed, which does not seem
|
3054
|
+
# desirable. We could remove the package's files and raise an
|
3055
|
+
# exception, but this seems the best approach to me.
|
3056
|
+
system(File.join(workdir, 'tpkg', 'postinstall'))
|
3057
|
+
if !$?.success?
|
3058
|
+
warn("Warning: postinstall for #{File.basename(package_file)} failed with exit value #{$?.exitstatus}")
|
3059
|
+
r = POSTINSTALL_ERR
|
3060
|
+
end
|
3061
|
+
ensure
|
3062
|
+
# Switch back to our previous directory
|
3063
|
+
Dir.chdir(pwd)
|
3064
|
+
end
|
3065
|
+
end
|
3066
|
+
r
|
3067
|
+
end
|
3068
|
+
|
3069
|
+
def run_externals_for_install(metadata, workdir, externals_to_skip)
|
3070
|
+
metadata[:externals].each do |external|
|
3071
|
+
# If the external references a datafile or datascript then read/run it
|
3072
|
+
# now that we've unpacked the package contents and have the file/script
|
3073
|
+
# available. This will get us the data for the external.
|
3074
|
+
if external[:datafile] || external[:datascript]
|
3075
|
+
pwd = Dir.pwd
|
3076
|
+
# chdir into the working directory so that the user can specify a
|
3077
|
+
# relative path to their file/script.
|
3078
|
+
Dir.chdir(File.join(workdir, 'tpkg'))
|
3079
|
+
if external[:datafile]
|
3080
|
+
# Read the file
|
3081
|
+
external[:data] = IO.read(external[:datafile])
|
3082
|
+
# Drop the datafile key so that we don't waste time re-reading the
|
3083
|
+
# datafile again in the future.
|
3084
|
+
external.delete(:datafile)
|
3085
|
+
elsif external[:datascript]
|
3086
|
+
# Run the script
|
3087
|
+
IO.popen(external[:datascript]) do |pipe|
|
3088
|
+
external[:data] = pipe.read
|
3089
|
+
end
|
3090
|
+
# Drop the datascript key so that we don't waste time re-running the
|
3091
|
+
# datascript again in the future.
|
3092
|
+
external.delete(:datascript)
|
3093
|
+
end
|
3094
|
+
# Switch back to our previous directory
|
3095
|
+
Dir.chdir(pwd)
|
3096
|
+
end
|
3097
|
+
if !externals_to_skip || !externals_to_skip.include?(external)
|
3098
|
+
run_external(metadata[:filename], :install, external[:name], external[:data])
|
3099
|
+
end
|
3100
|
+
end if metadata[:externals]
|
3101
|
+
end
|
3102
|
+
|
3103
|
+
def save_package_metadata(package_file, workdir, metadata, files_info, checksums_of_decrypted_files)
|
3104
|
+
# Save metadata for this pkg
|
3105
|
+
package_name = File.basename(package_file, File.extname(package_file))
|
3106
|
+
package_metadata_dir = File.join(@metadata_directory, package_name)
|
3107
|
+
FileUtils.mkdir_p(package_metadata_dir)
|
3108
|
+
metadata_file = File.new(File.join(package_metadata_dir, "tpkg.yml"), "w")
|
3109
|
+
metadata.write(metadata_file)
|
3110
|
+
metadata_file.close
|
3111
|
+
|
3112
|
+
# Save file_metadata.yml for this pkg
|
3113
|
+
if File.exist?(File.join(workdir, 'tpkg', 'file_metadata.bin'))
|
3114
|
+
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.bin')), 'bin')
|
3115
|
+
elsif File.exist?(File.join(workdir, 'tpkg', 'file_metadata.yml'))
|
3116
|
+
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.yml')), 'yml')
|
3117
|
+
elsif File.exists?(File.join(workdir, 'tpkg', 'file_metadata.xml'))
|
3118
|
+
file_metadata = FileMetadata.new(File.read(File.join(workdir, 'tpkg', 'file_metadata.xml')), 'xml')
|
3119
|
+
end
|
3120
|
+
if file_metadata
|
3121
|
+
file_metadata[:package_file] = File.basename(package_file)
|
3122
|
+
file_metadata[:files].each do |file|
|
3123
|
+
acl = files_info[file[:path]]
|
3124
|
+
file.merge!(acl) unless acl.nil?
|
3125
|
+
digest = checksums_of_decrypted_files[File.expand_path(file[:path])]
|
3126
|
+
if digest
|
3127
|
+
digests = file[:checksum][:digests]
|
3128
|
+
digests[0][:encrypted] = true
|
3129
|
+
digests[1] = {:decrypted => true, :value => digest}
|
3130
|
+
end
|
3131
|
+
end
|
3132
|
+
|
3133
|
+
file = File.open(File.join(package_metadata_dir, "file_metadata.bin"), "w")
|
3134
|
+
Marshal.dump(file_metadata.to_hash, file)
|
3135
|
+
file.close
|
3136
|
+
else
|
3137
|
+
warn "Warning: package #{File.basename(package_file)} does not include file_metadata information."
|
3138
|
+
end
|
3139
|
+
end
|
3140
|
+
|
3042
3141
|
def requirements_for_currently_installed_package(pkgname=nil)
|
3043
3142
|
requirements = []
|
3044
3143
|
metadata_for_installed_packages.each do |metadata|
|
@@ -3461,7 +3560,7 @@ class Tpkg
|
|
3461
3560
|
warn "Skipping #{File.basename(pkgfile)}, already installed"
|
3462
3561
|
else
|
3463
3562
|
if prompt_for_conflicting_files(pkgfile)
|
3464
|
-
ret_val |= unpack(pkgfile, passphrase)
|
3563
|
+
ret_val |= unpack(pkgfile, :passphrase => passphrase)
|
3465
3564
|
end
|
3466
3565
|
end
|
3467
3566
|
end
|
@@ -3697,7 +3796,7 @@ class Tpkg
|
|
3697
3796
|
end
|
3698
3797
|
end if pkg[:metadata][:dependencies]
|
3699
3798
|
if can_unpack
|
3700
|
-
ret_val |= unpack(pkgfile, passphrase, :externals_to_skip => externals_to_skip)
|
3799
|
+
ret_val |= unpack(pkgfile, :passphrase => passphrase, :externals_to_skip => externals_to_skip)
|
3701
3800
|
end
|
3702
3801
|
|
3703
3802
|
has_updates = true
|
@@ -3829,7 +3928,8 @@ class Tpkg
|
|
3829
3928
|
|
3830
3929
|
topleveldir = Tpkg::package_toplevel_directory(package_file)
|
3831
3930
|
workdir = Tpkg::tempdir(topleveldir, @tmp_directory)
|
3832
|
-
|
3931
|
+
extract_tpkg_tar_command = Tpkg::cmd_to_extract_tpkg_tar(package_file, topleveldir)
|
3932
|
+
system("#{extract_tpkg_tar_command} | #{@tar} #{@@taroptions} -C #{workdir} -xpf -")
|
3833
3933
|
|
3834
3934
|
# Run preremove script
|
3835
3935
|
if File.exist?(File.join(workdir, 'tpkg', 'preremove'))
|
data/lib/tpkg/deployer.rb
CHANGED
@@ -16,22 +16,12 @@ rescue LoadError
|
|
16
16
|
require 'rubygems'
|
17
17
|
require 'net/ssh'
|
18
18
|
end
|
19
|
-
#require 'highline/import'
|
20
19
|
|
21
20
|
class Deployer
|
22
|
-
|
23
|
-
# begin
|
24
|
-
# require 'rubygems'
|
25
|
-
# require 'net/ssh'
|
26
|
-
# require 'highline/import'
|
27
|
-
# rescue LoadError
|
28
|
-
# raise LoadError, "In order to use the deployment feature, you must have rubygems installed. Additionally, you need to install the following gems: net-ssh, highline"
|
29
|
-
# else
|
30
|
-
# super
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
|
21
|
+
|
34
22
|
def initialize(options = nil)
|
23
|
+
@sudo_pw = nil
|
24
|
+
@pw_prompts = {}
|
35
25
|
@mutex = Mutex.new
|
36
26
|
@max_worker = 4
|
37
27
|
@abort_on_failure = false
|
@@ -47,21 +37,12 @@ class Deployer
|
|
47
37
|
end
|
48
38
|
end
|
49
39
|
|
50
|
-
def prompt
|
51
|
-
user = prompt_username
|
52
|
-
password = prompt_password
|
53
|
-
return user, password
|
54
|
-
end
|
55
|
-
|
56
40
|
def prompt_username
|
57
|
-
|
58
|
-
user = $stdin.gets.chomp
|
59
|
-
return user
|
41
|
+
ask("Username: ")
|
60
42
|
end
|
61
43
|
|
62
44
|
def prompt_password
|
63
|
-
|
64
|
-
return password
|
45
|
+
ask("SSH Password (leave blank if using ssh key): ", true)
|
65
46
|
end
|
66
47
|
|
67
48
|
def ask(str,mask=false)
|
@@ -75,31 +56,29 @@ class Deployer
|
|
75
56
|
return input
|
76
57
|
end
|
77
58
|
|
78
|
-
$sudo_pw = nil
|
79
59
|
def get_sudo_pw
|
80
60
|
@mutex.synchronize {
|
81
|
-
if
|
82
|
-
|
61
|
+
if @sudo_pw.nil?
|
62
|
+
@sudo_pw = ask("Sudo password: ", true)
|
83
63
|
else
|
84
|
-
return
|
64
|
+
return @sudo_pw
|
85
65
|
end
|
86
66
|
}
|
87
67
|
end
|
88
68
|
|
89
|
-
|
90
|
-
|
69
|
+
# Prompt user for input and cache it. If in the future, we see
|
70
|
+
# the same prompt again, we can reuse the existing inputs. This saves
|
71
|
+
# the users from having to type in a bunch of inputs (such as password)
|
72
|
+
def get_input_for_pw_prompt(prompt)
|
91
73
|
@mutex.synchronize {
|
92
|
-
if
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
$passphrases[package] = ask(package, true)
|
97
|
-
else
|
98
|
-
return $passphrases[package]
|
99
|
-
end
|
74
|
+
if @pw_prompts[prompt].nil?
|
75
|
+
@pw_prompts[prompt] = ask(prompt, true)
|
76
|
+
end
|
77
|
+
return @pw_prompts[prompt]
|
100
78
|
}
|
101
79
|
end
|
102
80
|
|
81
|
+
# Return a block that can be used for executing a cmd on the remote server
|
103
82
|
def ssh_execute(server, username, password, cmd)
|
104
83
|
return lambda {
|
105
84
|
exit_status = 0
|
@@ -130,19 +109,14 @@ class Deployer
|
|
130
109
|
# for; now we can check to see if it's a password prompt, and
|
131
110
|
# interactively return data if so (see request_pty above).
|
132
111
|
channel.on_data do |ch, data|
|
133
|
-
if data =~ /Password
|
134
|
-
#sudo_password = (!password.nil && password != "" && password) || get_sudo_pw
|
112
|
+
if data =~ /Password:/
|
135
113
|
password = get_sudo_pw unless !password.nil? && password != ""
|
136
114
|
channel.send_data "#{password}\n"
|
137
|
-
elsif data
|
138
|
-
|
139
|
-
|
115
|
+
elsif data =~ /password/i or data =~ /passphrase/i or
|
116
|
+
data =~ /pass phrase/i or data =~ /incorrect passphrase/i
|
117
|
+
input = get_input_for_pw_prompt(data)
|
118
|
+
channel.send_data "#{input}\n"
|
140
119
|
else
|
141
|
-
# print "#{server}: #{data}" if $debug
|
142
|
-
# ssh channels can be treated as a hash for the specific purpose of
|
143
|
-
# getting values out of the block later
|
144
|
-
# channel[:result] ||= ""
|
145
|
-
# channel[:result] << data
|
146
120
|
result << data unless data.nil? or data.empty?
|
147
121
|
end
|
148
122
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tpkg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darren Dao
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2010-02-
|
13
|
+
date: 2010-02-27 00:00:00 +00:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
version: "0"
|
35
35
|
version:
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
name: kwalify
|
37
|
+
name: ddao-kwalify
|
38
38
|
type: :runtime
|
39
39
|
version_requirement:
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -55,24 +55,24 @@ extensions: []
|
|
55
55
|
extra_rdoc_files: []
|
56
56
|
|
57
57
|
files:
|
58
|
-
- bin/cpan2tpkg
|
59
|
-
- bin/tpkg_xml_to_yml
|
60
|
-
- bin/tpkg
|
61
|
-
- bin/gem2tpkg
|
62
|
-
- schema/tpkg.dtd
|
63
|
-
- schema/schema-1.0.yml
|
64
|
-
- schema/tpkg-1.0.0.dtd
|
65
|
-
- schema/tpkg-1.0.4.dtd
|
66
|
-
- schema/tpkg-1.0.3.dtd
|
67
|
-
- schema/schema.yml
|
68
|
-
- schema/tpkg-1.0.5.dtd
|
69
|
-
- schema/tpkg-1.0.2.dtd
|
70
|
-
- schema/tpkg-1.0.1.dtd
|
71
58
|
- lib/tpkg/versiontype.rb
|
72
59
|
- lib/tpkg/deployer.rb
|
73
60
|
- lib/tpkg/thread_pool.rb
|
74
61
|
- lib/tpkg/metadata.rb
|
75
62
|
- lib/tpkg.rb
|
63
|
+
- schema/tpkg-1.0.4.dtd
|
64
|
+
- schema/tpkg-1.0.0.dtd
|
65
|
+
- schema/tpkg-1.0.5.dtd
|
66
|
+
- schema/tpkg.dtd
|
67
|
+
- schema/tpkg-1.0.1.dtd
|
68
|
+
- schema/tpkg-1.0.3.dtd
|
69
|
+
- schema/schema.yml
|
70
|
+
- schema/tpkg-1.0.2.dtd
|
71
|
+
- schema/schema-1.0.yml
|
72
|
+
- bin/tpkg
|
73
|
+
- bin/cpan2tpkg
|
74
|
+
- bin/gem2tpkg
|
75
|
+
- bin/tpkg_xml_to_yml
|
76
76
|
- Rakefile
|
77
77
|
has_rdoc: true
|
78
78
|
homepage: http://tpkg.sourceforge.net
|