deb-s3-lock-fix 0.11.8.fix1
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.
- checksums.yaml +7 -0
- data/README.md +284 -0
- data/bin/deb-s3 +10 -0
- data/lib/deb/s3/cli.rb +763 -0
- data/lib/deb/s3/lock.rb +125 -0
- data/lib/deb/s3/manifest.rb +144 -0
- data/lib/deb/s3/package.rb +309 -0
- data/lib/deb/s3/release.rb +161 -0
- data/lib/deb/s3/templates/package.erb +66 -0
- data/lib/deb/s3/templates/release.erb +20 -0
- data/lib/deb/s3/utils.rb +115 -0
- data/lib/deb/s3.rb +6 -0
- metadata +111 -0
data/lib/deb/s3/lock.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "base64"
|
3
|
+
require "digest/md5"
|
4
|
+
require "etc"
|
5
|
+
require "socket"
|
6
|
+
require "tempfile"
|
7
|
+
require "securerandom"
|
8
|
+
|
9
|
+
class Deb::S3::Lock
|
10
|
+
attr_reader :user
|
11
|
+
attr_reader :host
|
12
|
+
|
13
|
+
def initialize(user, host)
|
14
|
+
@user = user
|
15
|
+
@host = host
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
#
|
20
|
+
# 2-phase mutual lock mechanism based on `s3:CopyObject`.
|
21
|
+
#
|
22
|
+
# This logic isn't relying on S3's enhanced features like Object Lock
|
23
|
+
# because it imposes some limitation on using other features like
|
24
|
+
# S3 Cross-Region replication. This should work more than good enough
|
25
|
+
# with S3's strong read-after-write consistency which we can presume
|
26
|
+
# in all region nowadays.
|
27
|
+
#
|
28
|
+
# This is relying on S3 to set object's ETag as object's MD5 if an
|
29
|
+
# object isn't comprized from multiple parts. We'd be able to presume
|
30
|
+
# it as the lock file is usually an object of some smaller bytes.
|
31
|
+
#
|
32
|
+
# acquire lock:
|
33
|
+
# 1. call `s3:HeadObject` on final lock object
|
34
|
+
# 1. If final lock object exists, restart from the beginning
|
35
|
+
# 2. Otherwise, call `s3:PutObject` to create initial lock object
|
36
|
+
# 2. Perform `s3:CopyObject` to copy from initial lock object
|
37
|
+
# to final lock object with specifying ETag/MD5 of the initial
|
38
|
+
# lock object
|
39
|
+
# 1. If copy object fails as `PreconditionFailed`, restart
|
40
|
+
# from the beginning
|
41
|
+
# 2. Otherwise, lock has been acquired
|
42
|
+
#
|
43
|
+
# release lock:
|
44
|
+
# 1. remove final lock object by `s3:DeleteObject`
|
45
|
+
#
|
46
|
+
def lock(codename, component = nil, architecture = nil, cache_control = nil, max_attempts=60, max_wait_interval=10)
|
47
|
+
uuid = SecureRandom.uuid
|
48
|
+
lockbody = "#{Etc.getlogin}@#{Socket.gethostname}-#{uuid}"
|
49
|
+
|
50
|
+
initial_lockfile = initial_lock_path(codename, component, architecture, cache_control)
|
51
|
+
final_lockfile = lock_path(codename, component, architecture, cache_control)
|
52
|
+
|
53
|
+
md5_b64 = Base64.encode64(Digest::MD5.digest(lockbody))
|
54
|
+
md5_hex = Digest::MD5.hexdigest(lockbody)
|
55
|
+
max_attempts.times do |i|
|
56
|
+
wait_interval = [(1<<i)/10, max_wait_interval].min
|
57
|
+
if Deb::S3::Utils.s3_exists?(final_lockfile)
|
58
|
+
lock = current(codename, component, architecture, cache_control)
|
59
|
+
$stderr.puts("Repository is locked by another user: #{lock.user} at host #{lock.host} (phase-1)")
|
60
|
+
$stderr.puts("Attempting to obtain a lock after #{wait_interval} secound(s).")
|
61
|
+
sleep(wait_interval)
|
62
|
+
else
|
63
|
+
# upload the file
|
64
|
+
Deb::S3::Utils.s3.put_object(
|
65
|
+
bucket: Deb::S3::Utils.bucket,
|
66
|
+
key: Deb::S3::Utils.s3_path(initial_lockfile),
|
67
|
+
body: lockbody,
|
68
|
+
content_type: "text/plain",
|
69
|
+
content_md5: md5_b64,
|
70
|
+
metadata: {
|
71
|
+
"md5" => md5_hex,
|
72
|
+
},
|
73
|
+
)
|
74
|
+
begin
|
75
|
+
Deb::S3::Utils.s3.copy_object(
|
76
|
+
bucket: Deb::S3::Utils.bucket,
|
77
|
+
key: Deb::S3::Utils.s3_path(final_lockfile),
|
78
|
+
copy_source: "/#{Deb::S3::Utils.bucket}/#{Deb::S3::Utils.s3_path(initial_lockfile)}",
|
79
|
+
copy_source_if_match: md5_hex,
|
80
|
+
)
|
81
|
+
return
|
82
|
+
rescue Aws::S3::Errors::PreconditionFailed => error
|
83
|
+
lock = current(codename, component, architecture, cache_control)
|
84
|
+
$stderr.puts("Repository is locked by another user: #{lock.user} at host #{lock.host} (phase-2)")
|
85
|
+
$stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).")
|
86
|
+
sleep(wait_interval)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# TODO: throw appropriate error class
|
91
|
+
raise("Unable to obtain a lock after #{max_attempts}, giving up.")
|
92
|
+
end
|
93
|
+
|
94
|
+
def unlock(codename, component = nil, architecture = nil, cache_control = nil)
|
95
|
+
Deb::S3::Utils.s3_remove(initial_lock_path(codename, component, architecture, cache_control))
|
96
|
+
Deb::S3::Utils.s3_remove(lock_path(codename, component, architecture, cache_control))
|
97
|
+
end
|
98
|
+
|
99
|
+
def current(codename, component = nil, architecture = nil, cache_control = nil)
|
100
|
+
lockbody = Deb::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control))
|
101
|
+
if lockbody
|
102
|
+
user, host_with_uuid = lockbody.to_s.split("@", 2)
|
103
|
+
lock = Deb::S3::Lock.new(user, host_with_uuid)
|
104
|
+
else
|
105
|
+
lock = Deb::S3::Lock.new("unknown", "unknown")
|
106
|
+
end
|
107
|
+
lock
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def initial_lock_path(codename, component = nil, architecture = nil, cache_control = nil)
|
112
|
+
"dists/#{codename}/lockfile.lock"
|
113
|
+
end
|
114
|
+
|
115
|
+
def lock_path(codename, component = nil, architecture = nil, cache_control = nil)
|
116
|
+
#
|
117
|
+
# Acquire repository lock at `codename` level to avoid race between concurrent upload attempts.
|
118
|
+
#
|
119
|
+
# * `deb-s3 upload --arch=all` touchs multiples of `dists/{codename}/{component}/binary-*/Packages*`
|
120
|
+
# * All `deb-s3 upload` touchs `dists/{codename}/Release`
|
121
|
+
#
|
122
|
+
"dists/#{codename}/lockfile"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "tempfile"
|
3
|
+
require "zlib"
|
4
|
+
require 'deb/s3/utils'
|
5
|
+
require 'deb/s3/package'
|
6
|
+
|
7
|
+
class Deb::S3::Manifest
|
8
|
+
include Deb::S3::Utils
|
9
|
+
|
10
|
+
attr_accessor :codename
|
11
|
+
attr_accessor :component
|
12
|
+
attr_accessor :cache_control
|
13
|
+
attr_accessor :architecture
|
14
|
+
attr_accessor :fail_if_exists
|
15
|
+
attr_accessor :skip_package_upload
|
16
|
+
|
17
|
+
attr_accessor :files
|
18
|
+
|
19
|
+
attr_reader :packages
|
20
|
+
attr_reader :packages_to_be_upload
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@packages = []
|
24
|
+
@packages_to_be_upload = []
|
25
|
+
@component = nil
|
26
|
+
@architecture = nil
|
27
|
+
@files = {}
|
28
|
+
@cache_control = ""
|
29
|
+
@fail_if_exists = false
|
30
|
+
@skip_package_upload = false
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def retrieve(codename, component, architecture, cache_control, fail_if_exists, skip_package_upload=false)
|
35
|
+
m = if s = Deb::S3::Utils.s3_read("dists/#{codename}/#{component}/binary-#{architecture}/Packages")
|
36
|
+
self.parse_packages(s)
|
37
|
+
else
|
38
|
+
self.new
|
39
|
+
end
|
40
|
+
|
41
|
+
m.codename = codename
|
42
|
+
m.component = component
|
43
|
+
m.architecture = architecture
|
44
|
+
m.cache_control = cache_control
|
45
|
+
m.fail_if_exists = fail_if_exists
|
46
|
+
m.skip_package_upload = skip_package_upload
|
47
|
+
m
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_packages(str)
|
51
|
+
m = self.new
|
52
|
+
str.split("\n\n").each do |s|
|
53
|
+
next if s.chomp.empty?
|
54
|
+
m.packages << Deb::S3::Package.parse_string(s)
|
55
|
+
end
|
56
|
+
m
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add(pkg, preserve_versions, needs_uploading=true)
|
61
|
+
if self.fail_if_exists
|
62
|
+
packages.each { |p|
|
63
|
+
next unless p.name == pkg.name && \
|
64
|
+
p.full_version == pkg.full_version && \
|
65
|
+
File.basename(p.url_filename(@codename)) != \
|
66
|
+
File.basename(pkg.url_filename(@codename))
|
67
|
+
raise AlreadyExistsError,
|
68
|
+
"package #{pkg.name}_#{pkg.full_version} already exists " \
|
69
|
+
"with different filename (#{p.url_filename(@codename)})"
|
70
|
+
}
|
71
|
+
end
|
72
|
+
if preserve_versions
|
73
|
+
packages.delete_if { |p| p.name == pkg.name && p.full_version == pkg.full_version }
|
74
|
+
else
|
75
|
+
packages.delete_if { |p| p.name == pkg.name }
|
76
|
+
end
|
77
|
+
packages << pkg
|
78
|
+
packages_to_be_upload << pkg if needs_uploading
|
79
|
+
pkg
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_package(pkg, versions=nil)
|
83
|
+
deleted = []
|
84
|
+
new_packages = @packages.select { |p|
|
85
|
+
# Include packages we didn't name
|
86
|
+
if p.name != pkg
|
87
|
+
p
|
88
|
+
# Also include the packages not matching a specified version
|
89
|
+
elsif (!versions.nil? and p.name == pkg and !versions.include?(p.version) and !versions.include?("#{p.version}-#{p.iteration}") and !versions.include?(p.full_version))
|
90
|
+
p
|
91
|
+
end
|
92
|
+
}
|
93
|
+
deleted = @packages - new_packages
|
94
|
+
@packages = new_packages
|
95
|
+
deleted
|
96
|
+
end
|
97
|
+
|
98
|
+
def generate
|
99
|
+
@packages.collect { |pkg| pkg.generate(@codename) }.join("\n")
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_to_s3
|
103
|
+
manifest = self.generate
|
104
|
+
|
105
|
+
unless self.skip_package_upload
|
106
|
+
# store any packages that need to be stored
|
107
|
+
@packages_to_be_upload.each do |pkg|
|
108
|
+
yield pkg.url_filename(@codename) if block_given?
|
109
|
+
s3_store(pkg.filename, pkg.url_filename(@codename), 'application/octet-stream; charset=binary', self.cache_control, self.fail_if_exists)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# generate the Packages file
|
114
|
+
pkgs_temp = Tempfile.new("Packages")
|
115
|
+
pkgs_temp.write manifest
|
116
|
+
pkgs_temp.close
|
117
|
+
f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages"
|
118
|
+
yield f if block_given?
|
119
|
+
s3_store(pkgs_temp.path, f, 'text/plain; charset=utf-8', self.cache_control)
|
120
|
+
@files["#{@component}/binary-#{@architecture}/Packages"] = hashfile(pkgs_temp.path)
|
121
|
+
pkgs_temp.unlink
|
122
|
+
|
123
|
+
# generate the Packages.gz file
|
124
|
+
gztemp = Tempfile.new("Packages.gz")
|
125
|
+
gztemp.close
|
126
|
+
Zlib::GzipWriter.open(gztemp.path) { |gz| gz.write manifest }
|
127
|
+
f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages.gz"
|
128
|
+
yield f if block_given?
|
129
|
+
s3_store(gztemp.path, f, 'application/x-gzip; charset=binary', self.cache_control)
|
130
|
+
@files["#{@component}/binary-#{@architecture}/Packages.gz"] = hashfile(gztemp.path)
|
131
|
+
gztemp.unlink
|
132
|
+
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
|
136
|
+
def hashfile(path)
|
137
|
+
{
|
138
|
+
:size => File.size(path),
|
139
|
+
:sha1 => Digest::SHA1.file(path).hexdigest,
|
140
|
+
:sha256 => Digest::SHA2.file(path).hexdigest,
|
141
|
+
:md5 => Digest::MD5.file(path).hexdigest
|
142
|
+
}
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "digest/sha1"
|
3
|
+
require "digest/sha2"
|
4
|
+
require "digest/md5"
|
5
|
+
require "open3"
|
6
|
+
require "socket"
|
7
|
+
require "tmpdir"
|
8
|
+
require "uri"
|
9
|
+
|
10
|
+
require 'deb/s3/utils'
|
11
|
+
|
12
|
+
class Deb::S3::Package
|
13
|
+
include Deb::S3::Utils
|
14
|
+
|
15
|
+
attr_accessor :name
|
16
|
+
attr_accessor :version
|
17
|
+
attr_accessor :epoch
|
18
|
+
attr_accessor :iteration
|
19
|
+
attr_accessor :maintainer
|
20
|
+
attr_accessor :vendor
|
21
|
+
attr_accessor :url
|
22
|
+
attr_accessor :category
|
23
|
+
attr_accessor :license
|
24
|
+
attr_accessor :architecture
|
25
|
+
attr_accessor :description
|
26
|
+
|
27
|
+
attr_accessor :dependencies
|
28
|
+
|
29
|
+
# Any other attributes specific to this package.
|
30
|
+
# This is where you'd put rpm, deb, or other specific attributes.
|
31
|
+
attr_accessor :attributes
|
32
|
+
|
33
|
+
# hashes
|
34
|
+
attr_accessor :sha1
|
35
|
+
attr_accessor :sha256
|
36
|
+
attr_accessor :md5
|
37
|
+
attr_accessor :size
|
38
|
+
|
39
|
+
attr_accessor :filename
|
40
|
+
|
41
|
+
class << self
|
42
|
+
include Deb::S3::Utils
|
43
|
+
|
44
|
+
def parse_file(package)
|
45
|
+
p = self.new
|
46
|
+
p.extract_info(extract_control(package))
|
47
|
+
p.apply_file_info(package)
|
48
|
+
p.filename = package
|
49
|
+
p
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_string(s)
|
53
|
+
p = self.new
|
54
|
+
p.extract_info(s)
|
55
|
+
p
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_control(package)
|
59
|
+
if system("which dpkg > /dev/null 2>&1")
|
60
|
+
output, status = Open3.capture2("dpkg", "-f", package)
|
61
|
+
output
|
62
|
+
else
|
63
|
+
# use ar to determine control file name (control.ext)
|
64
|
+
package_files = `ar t #{package}`
|
65
|
+
control_file = package_files.split("\n").select do |file|
|
66
|
+
file.start_with?("control.")
|
67
|
+
end.first
|
68
|
+
if control_file === "control.tar.gz"
|
69
|
+
compression = "z"
|
70
|
+
elsif control_file === "control.tar.zst"
|
71
|
+
compression = "I zstd"
|
72
|
+
else
|
73
|
+
compression = "J"
|
74
|
+
end
|
75
|
+
|
76
|
+
# ar fails to find the control.tar.gz tarball within the .deb
|
77
|
+
# on Mac OS. Try using ar to list the control file, if found,
|
78
|
+
# use ar to extract, otherwise attempt with tar which works on OS X.
|
79
|
+
extract_control_tarball_cmd = "ar p #{package} #{control_file}"
|
80
|
+
|
81
|
+
begin
|
82
|
+
safesystem("ar t #{package} #{control_file} &> /dev/null")
|
83
|
+
rescue SafeSystemError
|
84
|
+
warn "Failed to find control data in .deb with ar, trying tar."
|
85
|
+
extract_control_tarball_cmd = "tar -#{compression} -xf #{package} --to-stdout #{control_file}"
|
86
|
+
end
|
87
|
+
|
88
|
+
Dir.mktmpdir do |path|
|
89
|
+
safesystem("#{extract_control_tarball_cmd} | tar -#{compression} -xf - -C #{path}")
|
90
|
+
File.read(File.join(path, "control"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
@attributes = {}
|
98
|
+
|
99
|
+
# Reference
|
100
|
+
# http://www.debian.org/doc/manuals/maint-guide/first.en.html
|
101
|
+
# http://wiki.debian.org/DeveloperConfiguration
|
102
|
+
# https://github.com/jordansissel/fpm/issues/37
|
103
|
+
if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
|
104
|
+
# Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
|
105
|
+
@maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
|
106
|
+
else
|
107
|
+
# TODO(sissel): Maybe support using 'git config' for a default as well?
|
108
|
+
# git config --get user.name, etc can be useful.
|
109
|
+
#
|
110
|
+
# Otherwise default to user@currenthost
|
111
|
+
@maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
|
112
|
+
end
|
113
|
+
|
114
|
+
@name = nil
|
115
|
+
@architecture = "native"
|
116
|
+
@description = "no description given"
|
117
|
+
@version = nil
|
118
|
+
@epoch = nil
|
119
|
+
@iteration = nil
|
120
|
+
@url = nil
|
121
|
+
@category = "default"
|
122
|
+
@license = "unknown"
|
123
|
+
@vendor = "none"
|
124
|
+
@sha1 = nil
|
125
|
+
@sha256 = nil
|
126
|
+
@md5 = nil
|
127
|
+
@size = nil
|
128
|
+
@filename = nil
|
129
|
+
@url_filename = nil
|
130
|
+
|
131
|
+
@dependencies = []
|
132
|
+
end
|
133
|
+
|
134
|
+
def full_version
|
135
|
+
return nil if [epoch, version, iteration].all?(&:nil?)
|
136
|
+
[[epoch, version].compact.join(":"), iteration].compact.join("-")
|
137
|
+
end
|
138
|
+
|
139
|
+
def url_filename=(f)
|
140
|
+
@url_filename = f
|
141
|
+
end
|
142
|
+
|
143
|
+
def url_filename(codename)
|
144
|
+
@url_filename || "pool/#{codename}/#{self.name[0]}/#{self.name[0..1]}/#{File.basename(self.filename)}"
|
145
|
+
end
|
146
|
+
|
147
|
+
def generate(codename)
|
148
|
+
template("package.erb").result(binding)
|
149
|
+
end
|
150
|
+
|
151
|
+
# from fpm
|
152
|
+
def parse_depends(data)
|
153
|
+
return [] if data.nil? or data.empty?
|
154
|
+
# parse dependencies. Debian dependencies come in one of two forms:
|
155
|
+
# * name
|
156
|
+
# * name (op version)
|
157
|
+
# They are all on one line, separated by ", "
|
158
|
+
|
159
|
+
dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
|
160
|
+
return data.split(/, */).collect do |dep|
|
161
|
+
m = dep_re.match(dep)
|
162
|
+
if m
|
163
|
+
name, op, version = m.captures
|
164
|
+
# this is the proper form of dependency
|
165
|
+
if op && version && op != "" && version != ""
|
166
|
+
"#{name} (#{op} #{version})".strip
|
167
|
+
else
|
168
|
+
name.strip
|
169
|
+
end
|
170
|
+
else
|
171
|
+
# Assume normal form dependency, "name op version".
|
172
|
+
dep
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end # def parse_depends
|
176
|
+
|
177
|
+
# from fpm
|
178
|
+
def fix_dependency(dep)
|
179
|
+
# Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
|
180
|
+
# Convert anything that looks like 'NAME OP VERSION' to this format.
|
181
|
+
if dep =~ /[\(,\|]/
|
182
|
+
# Don't "fix" ones that could appear well formed already.
|
183
|
+
else
|
184
|
+
# Convert ones that appear to be 'name op version'
|
185
|
+
name, op, version = dep.split(/ +/)
|
186
|
+
if !version.nil?
|
187
|
+
# Convert strings 'foo >= bar' to 'foo (>= bar)'
|
188
|
+
dep = "#{name} (#{debianize_op(op)} #{version})"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
name_re = /^[^ \(]+/
|
193
|
+
name = dep[name_re]
|
194
|
+
if name =~ /[A-Z]/
|
195
|
+
dep = dep.gsub(name_re) { |n| n.downcase }
|
196
|
+
end
|
197
|
+
|
198
|
+
if dep.include?("_")
|
199
|
+
dep = dep.gsub("_", "-")
|
200
|
+
end
|
201
|
+
|
202
|
+
# Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
|
203
|
+
if dep =~ /\(~>/
|
204
|
+
name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
|
205
|
+
nextversion = version.split(".").collect { |v| v.to_i }
|
206
|
+
l = nextversion.length
|
207
|
+
nextversion[l-2] += 1
|
208
|
+
nextversion[l-1] = 0
|
209
|
+
nextversion = nextversion.join(".")
|
210
|
+
return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
211
|
+
elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
|
212
|
+
# Append this to conflicts
|
213
|
+
self.conflicts += [dep.gsub(/!=/,"=")]
|
214
|
+
return []
|
215
|
+
elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/)) and
|
216
|
+
self.attributes[:deb_ignore_iteration_in_dependencies?]
|
217
|
+
# Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
|
218
|
+
# but only when flag --ignore-iteration-in-dependencies is passed.
|
219
|
+
name, version = m[1..2]
|
220
|
+
nextversion = version.split('.').collect { |v| v.to_i }
|
221
|
+
nextversion[-1] += 1
|
222
|
+
nextversion = nextversion.join(".")
|
223
|
+
return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
|
224
|
+
else
|
225
|
+
# otherwise the dep is probably fine
|
226
|
+
return dep.rstrip
|
227
|
+
end
|
228
|
+
end # def fix_dependency
|
229
|
+
|
230
|
+
# from fpm
|
231
|
+
def extract_info(control)
|
232
|
+
fields = parse_control(control)
|
233
|
+
|
234
|
+
# Parse 'epoch:version-iteration' in the version string
|
235
|
+
full_version = fields.delete('Version')
|
236
|
+
if full_version !~ /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
|
237
|
+
raise "Unsupported version string '#{full_version}'"
|
238
|
+
end
|
239
|
+
self.epoch, self.version, self.iteration = $~.captures
|
240
|
+
|
241
|
+
self.architecture = fields.delete('Architecture')
|
242
|
+
self.category = fields.delete('Section')
|
243
|
+
self.license = fields.delete('License') || self.license
|
244
|
+
self.maintainer = fields.delete('Maintainer')
|
245
|
+
self.name = fields.delete('Package')
|
246
|
+
self.url = fields.delete('Homepage')
|
247
|
+
self.vendor = fields.delete('Vendor') || self.vendor
|
248
|
+
self.attributes[:deb_priority] = fields.delete('Priority')
|
249
|
+
self.attributes[:deb_origin] = fields.delete('Origin')
|
250
|
+
self.attributes[:deb_installed_size] = fields.delete('Installed-Size')
|
251
|
+
|
252
|
+
# Packages manifest fields
|
253
|
+
self.url_filename = fields.delete('Filename')
|
254
|
+
self.sha1 = fields.delete('SHA1')
|
255
|
+
self.sha256 = fields.delete('SHA256')
|
256
|
+
self.md5 = fields.delete('MD5sum')
|
257
|
+
self.size = fields.delete('Size')
|
258
|
+
self.description = fields.delete('Description')
|
259
|
+
|
260
|
+
#self.config_files = config_files
|
261
|
+
|
262
|
+
self.dependencies += Array(parse_depends(fields.delete('Depends')))
|
263
|
+
|
264
|
+
self.attributes[:deb_recommends] = fields.delete('Recommends')
|
265
|
+
self.attributes[:deb_suggests] = fields.delete('Suggests')
|
266
|
+
self.attributes[:deb_enhances] = fields.delete('Enhances')
|
267
|
+
self.attributes[:deb_pre_depends] = fields.delete('Pre-Depends')
|
268
|
+
|
269
|
+
self.attributes[:deb_breaks] = fields.delete('Breaks')
|
270
|
+
self.attributes[:deb_conflicts] = fields.delete('Conflicts')
|
271
|
+
self.attributes[:deb_provides] = fields.delete('Provides')
|
272
|
+
self.attributes[:deb_replaces] = fields.delete('Replaces')
|
273
|
+
|
274
|
+
self.attributes[:deb_field] = Hash[fields.map { |k, v|
|
275
|
+
[k.sub(/\AX[BCS]{0,3}-/, ''), v]
|
276
|
+
}]
|
277
|
+
end # def extract_info
|
278
|
+
|
279
|
+
def apply_file_info(file)
|
280
|
+
self.size = File.size(file)
|
281
|
+
self.sha1 = Digest::SHA1.file(file).hexdigest
|
282
|
+
self.sha256 = Digest::SHA2.file(file).hexdigest
|
283
|
+
self.md5 = Digest::MD5.file(file).hexdigest
|
284
|
+
end
|
285
|
+
|
286
|
+
def parse_control(control)
|
287
|
+
field = nil
|
288
|
+
value = ""
|
289
|
+
{}.tap do |fields|
|
290
|
+
control.each_line do |line|
|
291
|
+
if line =~ /^(\s+)(\S.*)$/
|
292
|
+
indent, rest = $1, $2
|
293
|
+
# Continuation
|
294
|
+
if indent.size == 1 && rest == "."
|
295
|
+
value << "\n"
|
296
|
+
rest = ""
|
297
|
+
elsif value.size > 0
|
298
|
+
value << "\n"
|
299
|
+
end
|
300
|
+
value << rest
|
301
|
+
elsif line =~ /^([-\w]+):(.*)$/
|
302
|
+
fields[field] = value if field
|
303
|
+
field, value = $1, $2.strip
|
304
|
+
end
|
305
|
+
end
|
306
|
+
fields[field] = value if field
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|