tpkg 1.16.2
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/Rakefile +18 -0
- data/bin/cpan2tpkg +348 -0
- data/bin/gem2tpkg +445 -0
- data/bin/tpkg +560 -0
- data/lib/tpkg.rb +3966 -0
- data/lib/tpkg/deployer.rb +220 -0
- data/lib/tpkg/metadata.rb +436 -0
- data/lib/tpkg/thread_pool.rb +108 -0
- data/lib/tpkg/versiontype.rb +84 -0
- metadata +85 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
# We store these gems in our thirdparty directory. So we need to add it
|
2
|
+
# it to the search path
|
3
|
+
# This one is for when everything is installed
|
4
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'thirdparty/net-ssh-2.0.11/lib'))
|
5
|
+
# And this one for when we're in the svn directory structure
|
6
|
+
$:.unshift(File.join(File.dirname(File.dirname(__FILE__)), 'thirdparty/net-ssh-2.0.11/lib'))
|
7
|
+
|
8
|
+
$debug = true
|
9
|
+
|
10
|
+
require 'thread_pool'
|
11
|
+
begin
|
12
|
+
# Try loading net-ssh w/o gems first so that we don't introduce a
|
13
|
+
# dependency on gems if it is not needed.
|
14
|
+
require 'net/ssh'
|
15
|
+
rescue LoadError
|
16
|
+
require 'rubygems'
|
17
|
+
require 'net/ssh'
|
18
|
+
end
|
19
|
+
#require 'highline/import'
|
20
|
+
|
21
|
+
class Deployer
|
22
|
+
# def self.new
|
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
|
+
|
34
|
+
def initialize(options = nil)
|
35
|
+
@mutex = Mutex.new
|
36
|
+
@max_worker = 4
|
37
|
+
@abort_on_failure = false
|
38
|
+
@use_ssh_key = false
|
39
|
+
@user = Etc.getlogin
|
40
|
+
@password = nil
|
41
|
+
unless options.nil?
|
42
|
+
@user = options["deploy-as"] unless options["deploy-as"].nil?
|
43
|
+
@password = options["deploy-password"] unless options["deploy-password"].nil?
|
44
|
+
@max_worker = options["max-worker"]
|
45
|
+
@abort_on_failure = options["abort-on-failure"]
|
46
|
+
@use_ssh_key = options["use-ssh-key"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def prompt
|
51
|
+
user = prompt_username
|
52
|
+
password = prompt_password
|
53
|
+
return user, password
|
54
|
+
end
|
55
|
+
|
56
|
+
def prompt_username
|
57
|
+
print "Username: "
|
58
|
+
user = $stdin.gets.chomp
|
59
|
+
return user
|
60
|
+
end
|
61
|
+
|
62
|
+
def prompt_password
|
63
|
+
password = ask("SSH Password (leave blank if using ssh key): ", true)
|
64
|
+
return password
|
65
|
+
end
|
66
|
+
|
67
|
+
def ask(str,mask=false)
|
68
|
+
begin
|
69
|
+
system 'stty -echo;' if mask
|
70
|
+
print str
|
71
|
+
input = STDIN.gets.chomp
|
72
|
+
ensure
|
73
|
+
system 'stty echo; echo ""'
|
74
|
+
end
|
75
|
+
return input
|
76
|
+
end
|
77
|
+
|
78
|
+
$sudo_pw = nil
|
79
|
+
def get_sudo_pw
|
80
|
+
@mutex.synchronize {
|
81
|
+
if $sudo_pw.nil?
|
82
|
+
$sudo_pw = ask("Sudo password: ", true)
|
83
|
+
else
|
84
|
+
return $sudo_pw
|
85
|
+
end
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
$passphrases = {}
|
90
|
+
def get_passphrase(package)
|
91
|
+
@mutex.synchronize {
|
92
|
+
if $passphrases[package].nil?
|
93
|
+
# $stdout.write package
|
94
|
+
# $stdout.flush
|
95
|
+
# $passphrases[package] = $stdin.gets.chomp
|
96
|
+
$passphrases[package] = ask(package, true)
|
97
|
+
else
|
98
|
+
return $passphrases[package]
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def ssh_execute(server, username, password, cmd)
|
104
|
+
return lambda {
|
105
|
+
exit_status = 0
|
106
|
+
result = []
|
107
|
+
|
108
|
+
begin
|
109
|
+
Net::SSH.start(server, username, :password => password) do |ssh|
|
110
|
+
puts "Connecting to #{server}"
|
111
|
+
ch = ssh.open_channel do |channel|
|
112
|
+
# now we request a "pty" (i.e. interactive) session so we can send data
|
113
|
+
# back and forth if needed. it WILL NOT WORK without this, and it has to
|
114
|
+
# be done before any call to exec.
|
115
|
+
|
116
|
+
channel.request_pty do |ch, success|
|
117
|
+
raise "Could not obtain pty (i.e. an interactive ssh session)" if !success
|
118
|
+
end
|
119
|
+
|
120
|
+
channel.exec(cmd) do |ch, success|
|
121
|
+
puts "Executing #{cmd} on #{server}"
|
122
|
+
# 'success' isn't related to bash exit codes or anything, but more
|
123
|
+
# about ssh internals (i think... not bash related anyways).
|
124
|
+
# not sure why it would fail at such a basic level, but it seems smart
|
125
|
+
# to do something about it.
|
126
|
+
abort "could not execute command" unless success
|
127
|
+
|
128
|
+
# on_data is a hook that fires when the loop that this block is fired
|
129
|
+
# in (see below) returns data. This is what we've been doing all this
|
130
|
+
# for; now we can check to see if it's a password prompt, and
|
131
|
+
# interactively return data if so (see request_pty above).
|
132
|
+
channel.on_data do |ch, data|
|
133
|
+
if data =~ /Password/
|
134
|
+
#sudo_password = (!password.nil && password != "" && password) || get_sudo_pw
|
135
|
+
password = get_sudo_pw unless !password.nil? && password != ""
|
136
|
+
channel.send_data "#{password}\n"
|
137
|
+
elsif data =~ /Passphrase/ or data =~ /pass phrase/ or data =~ /incorrect passphrase/i
|
138
|
+
passphrase = get_passphrase(data)
|
139
|
+
channel.send_data "#{passphrase}\n"
|
140
|
+
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
|
+
result << data unless data.nil? or data.empty?
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
channel.on_extended_data do |ch, type, data|
|
151
|
+
print "SSH command returned on stderr: #{data}"
|
152
|
+
end
|
153
|
+
|
154
|
+
channel.on_request "exit-status" do |ch, data|
|
155
|
+
exit_status = data.read_long
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
ch.wait
|
160
|
+
ssh.loop
|
161
|
+
end
|
162
|
+
if $debug
|
163
|
+
puts "==================================================\nResult from #{server}:"
|
164
|
+
puts result.join
|
165
|
+
puts "=================================================="
|
166
|
+
end
|
167
|
+
|
168
|
+
rescue Net::SSH::AuthenticationFailed
|
169
|
+
exit_status = 1
|
170
|
+
puts "Bad username/password combination"
|
171
|
+
rescue Exception => e
|
172
|
+
exit_status = 1
|
173
|
+
puts e.inspect
|
174
|
+
puts e.backtrace
|
175
|
+
puts "Can't connect to server"
|
176
|
+
end
|
177
|
+
|
178
|
+
return exit_status
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
# deploy_params is an array that holds the list of paramters that is used when invoking tpkg on to the remote
|
183
|
+
# servers where we want to deploy to.
|
184
|
+
#
|
185
|
+
# servers is an array or a callback that list the remote servers where we want to deploy to
|
186
|
+
def deploy(deploy_params, servers)
|
187
|
+
params = deploy_params.join(" ")
|
188
|
+
cmd = "tpkg #{params} -n"
|
189
|
+
user = @user
|
190
|
+
|
191
|
+
if @user.nil? && !@use_ssh_key
|
192
|
+
@user = prompt_username
|
193
|
+
end
|
194
|
+
|
195
|
+
if @password.nil? && !@use_ssh_key
|
196
|
+
@password = prompt_password
|
197
|
+
end
|
198
|
+
|
199
|
+
tp = ThreadPool.new(@max_worker)
|
200
|
+
statuses = {}
|
201
|
+
deploy_to = []
|
202
|
+
if servers.kind_of?(Proc)
|
203
|
+
deploy_to = servers.call
|
204
|
+
else
|
205
|
+
deploy_to = servers
|
206
|
+
end
|
207
|
+
|
208
|
+
deploy_to.each do | server |
|
209
|
+
tp.process do
|
210
|
+
status = ssh_execute(server, @user, @password, cmd).call
|
211
|
+
statuses[server] = status
|
212
|
+
end
|
213
|
+
end
|
214
|
+
tp.shutdown
|
215
|
+
puts "Exit statuses: "
|
216
|
+
puts statuses.inspect
|
217
|
+
|
218
|
+
return statuses
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module SymbolizeKeys
|
4
|
+
|
5
|
+
# converts any current string keys to symbol keys
|
6
|
+
def self.extended(hash)
|
7
|
+
hash.each do |key,value|
|
8
|
+
if key.is_a?(String)
|
9
|
+
hash.delete key
|
10
|
+
hash[key] = value #through overridden []=
|
11
|
+
end
|
12
|
+
if value.is_a?(Hash)
|
13
|
+
hash[key]=value.extend(SymbolizeKeys)
|
14
|
+
elsif value.is_a?(Array)
|
15
|
+
value.each do |val|
|
16
|
+
if val.is_a?(Hash)
|
17
|
+
val.extend(SymbolizeKeys)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# assigns a new key/value pair
|
25
|
+
# converts they key to a symbol if it is a string
|
26
|
+
def []=(*args)
|
27
|
+
args[0] = args[0].to_sym if args[0].is_a?(String)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
# returns new hash which is the merge of self and other hashes
|
32
|
+
# the returned hash will also be extended by SymbolizeKeys
|
33
|
+
def merge(*other_hashes , &resolution_proc )
|
34
|
+
merged = Hash.new.extend SymbolizeKeys
|
35
|
+
merged.merge! self , *other_hashes , &resolution_proc
|
36
|
+
end
|
37
|
+
|
38
|
+
# merges the other hashes into self
|
39
|
+
# if a proc is submitted , it's return will be the value for the key
|
40
|
+
def merge!( *other_hashes , &resolution_proc )
|
41
|
+
|
42
|
+
# default resolution: value of the other hash
|
43
|
+
resolution_proc ||= proc{ |key,oldval,newval| newval }
|
44
|
+
|
45
|
+
# merge each hash into self
|
46
|
+
other_hashes.each do |hash|
|
47
|
+
hash.each{ |k,v|
|
48
|
+
# assign new k/v into self, resolving conflicts with resolution_proc
|
49
|
+
self[k] = self.has_key?(k) ? resolution_proc[k,self[k],v] : v
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Metadata
|
58
|
+
attr_accessor :source
|
59
|
+
REQUIRED_FIELDS = [:name, :version, :maintainer]
|
60
|
+
|
61
|
+
# Cleans up a string to make it suitable for use in a filename
|
62
|
+
def self.clean_for_filename(dirtystring)
|
63
|
+
dirtystring.downcase.gsub(/[^\w]/, '')
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_pkgs_metadata_from_yml_doc(yml_doc, metadata=nil, source=nil)
|
67
|
+
metadata = {} if metadata.nil?
|
68
|
+
metadata_lists = yml_doc.split("---")
|
69
|
+
metadata_lists.each do | metadata_text |
|
70
|
+
if metadata_text =~ /^:?name:(.+)/
|
71
|
+
name = $1.strip
|
72
|
+
metadata[name] = [] if !metadata[name]
|
73
|
+
metadata[name] << Metadata.new(metadata_text,'yml', source)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return metadata
|
77
|
+
end
|
78
|
+
|
79
|
+
# metadata_text = text representation of the metadata
|
80
|
+
# format = yml, xml, json, etc.
|
81
|
+
def initialize(metadata_text, format, source=nil)
|
82
|
+
@hash = nil
|
83
|
+
@metadata_text = metadata_text
|
84
|
+
@format = format
|
85
|
+
@source = source
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](key)
|
89
|
+
return hash[key]
|
90
|
+
end
|
91
|
+
|
92
|
+
def []=(key,value)
|
93
|
+
hash[key]=value
|
94
|
+
end
|
95
|
+
|
96
|
+
def hash
|
97
|
+
if @hash
|
98
|
+
return @hash
|
99
|
+
end
|
100
|
+
|
101
|
+
if @format == 'yml'
|
102
|
+
hash = YAML::load(@metadata_text)
|
103
|
+
@hash = hash.extend(SymbolizeKeys)
|
104
|
+
else
|
105
|
+
@hash = metadata_xml_to_hash
|
106
|
+
end
|
107
|
+
return @hash
|
108
|
+
end
|
109
|
+
|
110
|
+
def write(file)
|
111
|
+
YAML::dump(hash, file)
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_files_list
|
115
|
+
end
|
116
|
+
|
117
|
+
def generate_package_filename
|
118
|
+
name = hash[:name]
|
119
|
+
version = hash[:version]
|
120
|
+
packageversion = nil
|
121
|
+
if hash[:package_version] && !hash[:package_version].to_s.empty?
|
122
|
+
packageversion = hash[:package_version]
|
123
|
+
end
|
124
|
+
package_filename = "#{name}-#{version}"
|
125
|
+
if packageversion
|
126
|
+
package_filename << "-#{packageversion}"
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
if hash[:operatingsystem] and !hash[:operatingsystem].empty?
|
131
|
+
if hash[:operatingsystem].length == 1
|
132
|
+
package_filename << "-#{Metadata::clean_for_filename(hash[:operatingsystem].first)}"
|
133
|
+
else
|
134
|
+
operatingsystems = hash[:operatingsystem].dup
|
135
|
+
# Genericize any equivalent operating systems
|
136
|
+
# FIXME: more generic handling of equivalent OSs is probably called for
|
137
|
+
operatingsystems.each do |os|
|
138
|
+
os.sub!('CentOS', 'RedHat')
|
139
|
+
end
|
140
|
+
firstname = operatingsystems.first.split('-').first
|
141
|
+
firstversion = operatingsystems.first.split('-').last
|
142
|
+
if operatingsystems.all? { |os| os == operatingsystems.first }
|
143
|
+
# After genericizing all OSs are the same
|
144
|
+
package_filename << "-#{Metadata::clean_for_filename(operatingsystems.first)}"
|
145
|
+
elsif operatingsystems.all? { |os| os =~ /#{firstname}-/ }
|
146
|
+
# All of the OSs have the same name, just different versions. It
|
147
|
+
# may not be perfect, but name the package after the OS without a
|
148
|
+
# version. I.e. if the package specifies RedHat-4,RedHat-5 then
|
149
|
+
# name it "redhat". It might be confusing when it won't install on
|
150
|
+
# RedHat-3, but it seems better to me than naming it "multios".
|
151
|
+
package_filename << "-#{Metadata::clean_for_filename(firstname)}"
|
152
|
+
else
|
153
|
+
package_filename << "-multios"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
if hash[:architecture] and !hash[:architecture].empty?
|
158
|
+
if hash[:architecture].length == 1
|
159
|
+
package_filename << "-#{Metadata::clean_for_filename(hash[:architecture].first)}"
|
160
|
+
else
|
161
|
+
package_filename << "-multiarch"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
return package_filename
|
166
|
+
end
|
167
|
+
|
168
|
+
def verify_required_fields
|
169
|
+
REQUIRED_FIELDS.each do |reqfield|
|
170
|
+
if hash[reqfield].nil?
|
171
|
+
raise "Required field #{reqfield} not found"
|
172
|
+
elsif hash[reqfield].to_s.empty?
|
173
|
+
raise "Required field #{reqfield} is empty"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def metadata_xml_to_hash
|
179
|
+
# Don't do anything if metadata is from xml file
|
180
|
+
return if @format != "xml"
|
181
|
+
|
182
|
+
metadata_hash = {}
|
183
|
+
metadata_xml = REXML::Document.new(@metadata_text)
|
184
|
+
metadata_hash[:filename] = metadata_xml.root.attributes['filename']
|
185
|
+
|
186
|
+
REQUIRED_FIELDS.each do |reqfield|
|
187
|
+
if metadata_xml.elements["/tpkg/#{reqfield}"]
|
188
|
+
metadata_hash[reqfield] = metadata_xml.elements["/tpkg/#{reqfield}"].text
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
[:package_version, :description, :bugreporting].each do |optfield|
|
193
|
+
if metadata_xml.elements["/tpkg/#{optfield.to_s}"]
|
194
|
+
metadata_hash[optfield] =
|
195
|
+
metadata_xml.elements["/tpkg/#{optfield.to_s}"].text
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
[:operatingsystem, :architecture].each do |arrayfield|
|
200
|
+
array = []
|
201
|
+
# In the tpkg design docs I wrote that the user would specify
|
202
|
+
# multiple OSs or architectures by specifying the associated XML
|
203
|
+
# element more than once:
|
204
|
+
# <tpkg>
|
205
|
+
# <operatingsystem>RedHat-4</operatingsystem>
|
206
|
+
# <operatingsystem>CentOS-4</operatingsystem>
|
207
|
+
# </tpkg>
|
208
|
+
# However, I wrote the initial code and built my initial packages
|
209
|
+
# using comma separated values in a single instance of the
|
210
|
+
# element:
|
211
|
+
# <tpkg>
|
212
|
+
# <operatingsystem>RedHat-4,CentOS-4</operatingsystem>
|
213
|
+
# </tpkg>
|
214
|
+
# So we support both.
|
215
|
+
metadata_xml.elements.each("/tpkg/#{arrayfield.to_s}") do |af|
|
216
|
+
array.concat(af.text.split(/\s*,\s*/))
|
217
|
+
end
|
218
|
+
metadata_hash[arrayfield] = array unless array.empty?
|
219
|
+
end
|
220
|
+
|
221
|
+
deps = []
|
222
|
+
metadata_xml.elements.each('/tpkg/dependencies/dependency') do |depxml|
|
223
|
+
dep = {}
|
224
|
+
dep[:name] = depxml.elements['name'].text
|
225
|
+
[:allowed_versions, :minimum_version, :maximum_version,
|
226
|
+
:minimum_package_version, :maximum_package_version].each do |depfield|
|
227
|
+
if depxml.elements[depfield.to_s]
|
228
|
+
dep[depfield] = depxml.elements[depfield.to_s].text
|
229
|
+
end
|
230
|
+
end
|
231
|
+
if depxml.elements['native']
|
232
|
+
dep[:type] = :native
|
233
|
+
end
|
234
|
+
deps << dep
|
235
|
+
end
|
236
|
+
metadata_hash[:dependencies] = deps unless deps.empty?
|
237
|
+
|
238
|
+
conflicts = []
|
239
|
+
metadata_xml.elements.each('/tpkg/conflicts/conflict') do |conflictxml|
|
240
|
+
conflict = {}
|
241
|
+
conflict[:name] = conflictxml.elements['name'].text
|
242
|
+
[:minimum_version, :maximum_version,
|
243
|
+
:minimum_package_version, :maximum_package_version].each do |conflictfield|
|
244
|
+
if conflictxml.elements[conflictfield.to_s]
|
245
|
+
conflict[conflictfield] = conflictxml.elements[conflictfield.to_s].text
|
246
|
+
end
|
247
|
+
end
|
248
|
+
if conflictxml.elements['native']
|
249
|
+
conflict[:type] = :native
|
250
|
+
end
|
251
|
+
conflicts << conflict
|
252
|
+
end
|
253
|
+
metadata_hash[:conflicts] = conflicts unless conflicts.empty?
|
254
|
+
|
255
|
+
externals = []
|
256
|
+
metadata_xml.elements.each('/tpkg/externals/external') do |extxml|
|
257
|
+
external = {}
|
258
|
+
external[:name] = extxml.elements['name'].text
|
259
|
+
if extxml.elements['data']
|
260
|
+
external[:data] = extxml.elements['data'].text
|
261
|
+
elsif extxml.elements['datafile']
|
262
|
+
# We don't have access to the package contents here, so we just save
|
263
|
+
# the name of the file and leave it up to others to read the file
|
264
|
+
# when the package contents are available.
|
265
|
+
external[:datafile] = extxml.elements['datafile'].text
|
266
|
+
elsif extxml.elements['datascript']
|
267
|
+
# We don't have access to the package contents here, so we just save
|
268
|
+
# the name of the script and leave it up to others to run the script
|
269
|
+
# when the package contents are available.
|
270
|
+
external[:datascript] = extxml.elements['datascript'].text
|
271
|
+
end
|
272
|
+
externals << external
|
273
|
+
end
|
274
|
+
metadata_hash[:externals] = externals unless externals.empty?
|
275
|
+
|
276
|
+
metadata_hash[:files] = {}
|
277
|
+
file_defaults = {}
|
278
|
+
if metadata_xml.elements['/tpkg/files/file_defaults/posix']
|
279
|
+
posix = {}
|
280
|
+
if metadata_xml.elements['/tpkg/files/file_defaults/posix/owner']
|
281
|
+
owner =
|
282
|
+
metadata_xml.elements['/tpkg/files/file_defaults/posix/owner'].text
|
283
|
+
posix[:owner] = owner
|
284
|
+
|
285
|
+
end
|
286
|
+
gid = nil
|
287
|
+
if metadata_xml.elements['/tpkg/files/file_defaults/posix/group']
|
288
|
+
group =
|
289
|
+
metadata_xml.elements['/tpkg/files/file_defaults/posix/group'].text
|
290
|
+
posix[:group] = group
|
291
|
+
end
|
292
|
+
perms = nil
|
293
|
+
if metadata_xml.elements['/tpkg/files/file_defaults/posix/perms']
|
294
|
+
perms =
|
295
|
+
metadata_xml.elements['/tpkg/files/file_defaults/posix/perms'].text
|
296
|
+
posix[:perms] = perms.oct
|
297
|
+
end
|
298
|
+
file_defaults[:posix] = posix
|
299
|
+
end
|
300
|
+
metadata_hash[:files][:file_defaults] = file_defaults unless file_defaults.empty?
|
301
|
+
|
302
|
+
dir_defaults = {}
|
303
|
+
if metadata_xml.elements['/tpkg/files/dir_defaults/posix']
|
304
|
+
posix = {}
|
305
|
+
if metadata_xml.elements['/tpkg/files/dir_defaults/posix/owner']
|
306
|
+
owner =
|
307
|
+
metadata_xml.elements['/tpkg/files/dir_defaults/posix/owner'].text
|
308
|
+
posix[:owner] = owner
|
309
|
+
end
|
310
|
+
gid = nil
|
311
|
+
if metadata_xml.elements['/tpkg/files/dir_defaults/posix/group']
|
312
|
+
group =
|
313
|
+
metadata_xml.elements['/tpkg/files/dir_defaults/posix/group'].text
|
314
|
+
posix[:group] = group
|
315
|
+
end
|
316
|
+
perms = nil
|
317
|
+
if metadata_xml.elements['/tpkg/files/dir_defaults/posix/perms']
|
318
|
+
perms =
|
319
|
+
metadata_xml.elements['/tpkg/files/dir_defaults/posix/perms'].text
|
320
|
+
posix[:perms] = perms.oct
|
321
|
+
end
|
322
|
+
dir_defaults[:posix] = posix
|
323
|
+
end
|
324
|
+
metadata_hash[:files][:dir_defaults] = dir_defaults unless dir_defaults.empty?
|
325
|
+
|
326
|
+
files = []
|
327
|
+
metadata_xml.elements.each('/tpkg/files/file') do |filexml|
|
328
|
+
file = {}
|
329
|
+
file[:path] = filexml.elements['path'].text
|
330
|
+
if filexml.elements['encrypt']
|
331
|
+
encrypt = true
|
332
|
+
if filexml.elements['encrypt'].attribute('precrypt') &&
|
333
|
+
filexml.elements['encrypt'].attribute('precrypt').value == 'true'
|
334
|
+
encrypt = "precrypt"
|
335
|
+
end
|
336
|
+
file[:encrypt] = encrypt
|
337
|
+
end
|
338
|
+
if filexml.elements['init']
|
339
|
+
init = {}
|
340
|
+
if filexml.elements['init/start']
|
341
|
+
init[:start] = filexml.elements['init/start'].text
|
342
|
+
end
|
343
|
+
if filexml.elements['init/levels']
|
344
|
+
if filexml.elements['init/levels'].text
|
345
|
+
# Split '234' into ['2','3','4'], for example
|
346
|
+
init[:levels] = filexml.elements['init/levels'].text.split(//)
|
347
|
+
else
|
348
|
+
# If the element is empty in the XML (<levels/> or
|
349
|
+
# <levels></levels>) then we get nil back from the .text
|
350
|
+
# call, interpret that as no levels
|
351
|
+
init[:levels] = []
|
352
|
+
end
|
353
|
+
end
|
354
|
+
file[:init] = init
|
355
|
+
end
|
356
|
+
if filexml.elements['crontab']
|
357
|
+
crontab = {}
|
358
|
+
if filexml.elements['crontab/user']
|
359
|
+
crontab[:user] = filexml.elements['crontab/user'].text
|
360
|
+
end
|
361
|
+
file[:crontab] = crontab
|
362
|
+
end
|
363
|
+
if filexml.elements['posix']
|
364
|
+
posix = {}
|
365
|
+
if filexml.elements['posix/owner']
|
366
|
+
owner = filexml.elements['posix/owner'].text
|
367
|
+
posix[:owner] = owner
|
368
|
+
end
|
369
|
+
gid = nil
|
370
|
+
if filexml.elements['posix/group']
|
371
|
+
group = filexml.elements['posix/group'].text
|
372
|
+
posix[:group] = group
|
373
|
+
end
|
374
|
+
perms = nil
|
375
|
+
if filexml.elements['posix/perms']
|
376
|
+
perms = filexml.elements['posix/perms'].text
|
377
|
+
posix[:perms] = perms.oct
|
378
|
+
end
|
379
|
+
file[:posix] = posix
|
380
|
+
end
|
381
|
+
files << file
|
382
|
+
end
|
383
|
+
metadata_hash[:files][:files] = files unless files.empty?
|
384
|
+
|
385
|
+
return metadata_hash
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
class FileMetadata < Metadata
|
390
|
+
def hash
|
391
|
+
if @hash
|
392
|
+
return @hash
|
393
|
+
end
|
394
|
+
|
395
|
+
if @format == 'bin'
|
396
|
+
@hash = Marshal::load(@metadata_text)
|
397
|
+
@hash = hash.extend(SymbolizeKeys)
|
398
|
+
elsif @format == 'yml'
|
399
|
+
hash = YAML::load(@metadata_text)
|
400
|
+
@hash = hash.extend(SymbolizeKeys)
|
401
|
+
elsif @format == 'xml'
|
402
|
+
@hash = file_metadata_xml_to_hash
|
403
|
+
end
|
404
|
+
return @hash
|
405
|
+
end
|
406
|
+
|
407
|
+
def file_metadata_xml_to_hash
|
408
|
+
return if @format != "xml"
|
409
|
+
|
410
|
+
file_metadata_hash = {}
|
411
|
+
files = []
|
412
|
+
file_metadata_xml = REXML::Document.new(@metadata_text)
|
413
|
+
file_metadata_hash[:package_file] = file_metadata_xml.root.attributes['package_file']
|
414
|
+
file_metadata_xml.elements.each("files/file") do | file_ele |
|
415
|
+
file = {}
|
416
|
+
file[:path] = file_ele.elements['path'].text
|
417
|
+
file[:relocatable] = file_ele.attributes["relocatable"] == "true"
|
418
|
+
|
419
|
+
if file_ele.elements["checksum"]
|
420
|
+
digests = []
|
421
|
+
file_ele.elements.each("checksum/digest") do | digest_ele |
|
422
|
+
digest = {}
|
423
|
+
digest['value'] = digest_ele.text
|
424
|
+
digest['encrypted'] = digest_ele.attributes['encrypted'] && digest_ele.attributes['encrypted'] == "true"
|
425
|
+
digest['decrypted'] = digest_ele.attributes['decrypted'] && digest_ele.attributes['decrypted'] == "true"
|
426
|
+
digests << digest
|
427
|
+
end
|
428
|
+
checksum = {:digests => digests, :algorithm => file_ele.elements["checksum"].elements["algorithm"]}
|
429
|
+
end
|
430
|
+
file[:checksum] = checksum
|
431
|
+
files << file
|
432
|
+
end
|
433
|
+
file_metadata_hash[:files] = files
|
434
|
+
return file_metadata_hash
|
435
|
+
end
|
436
|
+
end
|