deb-s3-patched 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "tempfile"
3
+ require "socket"
4
+ require "etc"
5
+
6
+ class Deb::S3::Lock
7
+ attr_accessor :user
8
+ attr_accessor :host
9
+
10
+ def initialize
11
+ @user = nil
12
+ @host = nil
13
+ end
14
+
15
+ class << self
16
+ def locked?(codename, component = nil, architecture = nil, cache_control = nil)
17
+ Deb::S3::Utils.s3_exists?(lock_path(codename, component, architecture, cache_control))
18
+ end
19
+
20
+ def wait_for_lock(codename, component = nil, architecture = nil, cache_control = nil, max_attempts=60, wait=10)
21
+ attempts = 0
22
+ while self.locked?(codename, component, architecture, cache_control) do
23
+ attempts += 1
24
+ throw "Unable to obtain a lock after #{max_attempts}, giving up." if attempts > max_attempts
25
+ sleep(wait)
26
+ end
27
+ end
28
+
29
+ def lock(codename, component = nil, architecture = nil, cache_control = nil)
30
+ lockfile = Tempfile.new("lockfile")
31
+ lockfile.write("#{Etc.getlogin}@#{Socket.gethostname}")
32
+ lockfile.close
33
+
34
+ Deb::S3::Utils.s3_store(lockfile.path,
35
+ lock_path(codename, component, architecture, cache_control),
36
+ "text/plain",
37
+ cache_control)
38
+ end
39
+
40
+ def unlock(codename, component = nil, architecture = nil, cache_control = nil)
41
+ Deb::S3::Utils.s3_remove(lock_path(codename, component, architecture, cache_control))
42
+ end
43
+
44
+ def current(codename, component = nil, architecture = nil, cache_control = nil)
45
+ lock_content = Deb::S3::Utils.s3_read(lock_path(codename, component, architecture, cache_control))
46
+ lock_content = lock_content.split('@')
47
+ lock = Deb::S3::Lock.new
48
+ lock.user = lock_content[0]
49
+ lock.host = lock_content[1] if lock_content.size > 1
50
+ lock
51
+ end
52
+
53
+ private
54
+ def lock_path(codename, component = nil, architecture = nil, cache_control = nil)
55
+ "dists/#{codename}/#{component}/binary-#{architecture}/lockfile"
56
+ end
57
+ end
58
+ 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,301 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "digest/sha1"
3
+ require "digest/sha2"
4
+ require "digest/md5"
5
+ require "socket"
6
+ require "tmpdir"
7
+ require "uri"
8
+
9
+ require 'deb/s3/utils'
10
+
11
+ class Deb::S3::Package
12
+ include Deb::S3::Utils
13
+
14
+ attr_accessor :name
15
+ attr_accessor :version
16
+ attr_accessor :epoch
17
+ attr_accessor :iteration
18
+ attr_accessor :maintainer
19
+ attr_accessor :vendor
20
+ attr_accessor :url
21
+ attr_accessor :category
22
+ attr_accessor :license
23
+ attr_accessor :architecture
24
+ attr_accessor :description
25
+
26
+ attr_accessor :dependencies
27
+
28
+ # Any other attributes specific to this package.
29
+ # This is where you'd put rpm, deb, or other specific attributes.
30
+ attr_accessor :attributes
31
+
32
+ # hashes
33
+ attr_accessor :url_filename
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
+ `dpkg -f #{package}`
61
+ else
62
+ # ar fails to find the control.tar.gz tarball within the .deb
63
+ # on Mac OS. Try using ar to list the control file, if found,
64
+ # use ar to extract, otherwise attempt with tar which works on OS X.
65
+ extract_control_tarball_cmd = "ar p #{package} control.tar.gz"
66
+
67
+ begin
68
+ safesystem("ar t #{package} control.tar.gz &> /dev/null")
69
+ rescue SafeSystemError
70
+ warn "Failed to find control data in .deb with ar, trying tar."
71
+ extract_control_tarball_cmd = "tar zxf #{package} --to-stdout control.tar.gz"
72
+ end
73
+
74
+ Dir.mktmpdir do |path|
75
+ safesystem("#{extract_control_tarball_cmd} | tar -zxf - -C #{path}")
76
+ File.read(File.join(path, "control"))
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def initialize
83
+ @attributes = {}
84
+
85
+ # Reference
86
+ # http://www.debian.org/doc/manuals/maint-guide/first.en.html
87
+ # http://wiki.debian.org/DeveloperConfiguration
88
+ # https://github.com/jordansissel/fpm/issues/37
89
+ if ENV.include?("DEBEMAIL") and ENV.include?("DEBFULLNAME")
90
+ # Use DEBEMAIL and DEBFULLNAME as the default maintainer if available.
91
+ @maintainer = "#{ENV["DEBFULLNAME"]} <#{ENV["DEBEMAIL"]}>"
92
+ else
93
+ # TODO(sissel): Maybe support using 'git config' for a default as well?
94
+ # git config --get user.name, etc can be useful.
95
+ #
96
+ # Otherwise default to user@currenthost
97
+ @maintainer = "<#{ENV["USER"]}@#{Socket.gethostname}>"
98
+ end
99
+
100
+ @name = nil
101
+ @architecture = "native"
102
+ @description = "no description given"
103
+ @version = nil
104
+ @epoch = nil
105
+ @iteration = nil
106
+ @url = nil
107
+ @category = "default"
108
+ @license = "unknown"
109
+ @vendor = "none"
110
+ @sha1 = nil
111
+ @sha256 = nil
112
+ @md5 = nil
113
+ @size = nil
114
+ @filename = nil
115
+ @url_filename = nil
116
+
117
+ @dependencies = []
118
+ end
119
+
120
+ def full_version
121
+ return nil if [epoch, version, iteration].all?(&:nil?)
122
+ [[epoch, version].compact.join(":"), iteration].compact.join("-")
123
+ end
124
+
125
+ def filename=(f)
126
+ @filename = f
127
+ @filename
128
+ end
129
+
130
+ def url_filename(codename)
131
+ @url_filename || "pool/#{codename}/#{self.name[0]}/#{self.name[0..1]}/#{File.basename(self.filename)}"
132
+ end
133
+
134
+ def url_filename_encoded(codename)
135
+ @url_filename || "pool/#{codename}/#{self.name[0]}/#{self.name[0..1]}/#{s3_escape(File.basename(self.filename))}"
136
+ end
137
+
138
+ def generate(codename)
139
+ template("package.erb").result(binding)
140
+ end
141
+
142
+ # from fpm
143
+ def parse_depends(data)
144
+ return [] if data.nil? or data.empty?
145
+ # parse dependencies. Debian dependencies come in one of two forms:
146
+ # * name
147
+ # * name (op version)
148
+ # They are all on one line, separated by ", "
149
+
150
+ dep_re = /^([^ ]+)(?: \(([>=<]+) ([^)]+)\))?$/
151
+ return data.split(/, */).collect do |dep|
152
+ m = dep_re.match(dep)
153
+ if m
154
+ name, op, version = m.captures
155
+ # this is the proper form of dependency
156
+ if op && version && op != "" && version != ""
157
+ "#{name} (#{op} #{version})".strip
158
+ else
159
+ name.strip
160
+ end
161
+ else
162
+ # Assume normal form dependency, "name op version".
163
+ dep
164
+ end
165
+ end
166
+ end # def parse_depends
167
+
168
+ # from fpm
169
+ def fix_dependency(dep)
170
+ # Deb dependencies are: NAME (OP VERSION), like "zsh (> 3.0)"
171
+ # Convert anything that looks like 'NAME OP VERSION' to this format.
172
+ if dep =~ /[\(,\|]/
173
+ # Don't "fix" ones that could appear well formed already.
174
+ else
175
+ # Convert ones that appear to be 'name op version'
176
+ name, op, version = dep.split(/ +/)
177
+ if !version.nil?
178
+ # Convert strings 'foo >= bar' to 'foo (>= bar)'
179
+ dep = "#{name} (#{debianize_op(op)} #{version})"
180
+ end
181
+ end
182
+
183
+ name_re = /^[^ \(]+/
184
+ name = dep[name_re]
185
+ if name =~ /[A-Z]/
186
+ dep = dep.gsub(name_re) { |n| n.downcase }
187
+ end
188
+
189
+ if dep.include?("_")
190
+ dep = dep.gsub("_", "-")
191
+ end
192
+
193
+ # Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
194
+ if dep =~ /\(~>/
195
+ name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
196
+ nextversion = version.split(".").collect { |v| v.to_i }
197
+ l = nextversion.length
198
+ nextversion[l-2] += 1
199
+ nextversion[l-1] = 0
200
+ nextversion = nextversion.join(".")
201
+ return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
202
+ elsif (m = dep.match(/(\S+)\s+\(!= (.+)\)/))
203
+ # Append this to conflicts
204
+ self.conflicts += [dep.gsub(/!=/,"=")]
205
+ return []
206
+ elsif (m = dep.match(/(\S+)\s+\(= (.+)\)/)) and
207
+ self.attributes[:deb_ignore_iteration_in_dependencies?]
208
+ # Convert 'foo (= x)' to 'foo (>= x)' and 'foo (<< x+1)'
209
+ # but only when flag --ignore-iteration-in-dependencies is passed.
210
+ name, version = m[1..2]
211
+ nextversion = version.split('.').collect { |v| v.to_i }
212
+ nextversion[-1] += 1
213
+ nextversion = nextversion.join(".")
214
+ return ["#{name} (>= #{version})", "#{name} (<< #{nextversion})"]
215
+ else
216
+ # otherwise the dep is probably fine
217
+ return dep.rstrip
218
+ end
219
+ end # def fix_dependency
220
+
221
+ # from fpm
222
+ def extract_info(control)
223
+ fields = parse_control(control)
224
+
225
+ # Parse 'epoch:version-iteration' in the version string
226
+ full_version = fields.delete('Version')
227
+ if full_version !~ /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
228
+ raise "Unsupported version string '#{full_version}'"
229
+ end
230
+ self.epoch, self.version, self.iteration = $~.captures
231
+
232
+ self.architecture = fields.delete('Architecture')
233
+ self.category = fields.delete('Section')
234
+ self.license = fields.delete('License') || self.license
235
+ self.maintainer = fields.delete('Maintainer')
236
+ self.name = fields.delete('Package')
237
+ self.url = fields.delete('Homepage')
238
+ self.vendor = fields.delete('Vendor') || self.vendor
239
+ self.attributes[:deb_priority] = fields.delete('Priority')
240
+ self.attributes[:deb_origin] = fields.delete('Origin')
241
+ self.attributes[:deb_installed_size] = fields.delete('Installed-Size')
242
+
243
+ # Packages manifest fields
244
+ filename = fields.delete('Filename')
245
+ self.url_filename = filename && URI.unescape(filename)
246
+ self.sha1 = fields.delete('SHA1')
247
+ self.sha256 = fields.delete('SHA256')
248
+ self.md5 = fields.delete('MD5sum')
249
+ self.size = fields.delete('Size')
250
+ self.description = fields.delete('Description')
251
+
252
+ #self.config_files = config_files
253
+
254
+ self.dependencies += Array(parse_depends(fields.delete('Depends')))
255
+
256
+ self.attributes[:deb_recommends] = fields.delete('Recommends')
257
+ self.attributes[:deb_suggests] = fields.delete('Suggests')
258
+ self.attributes[:deb_enhances] = fields.delete('Enhances')
259
+ self.attributes[:deb_pre_depends] = fields.delete('Pre-Depends')
260
+
261
+ self.attributes[:deb_breaks] = fields.delete('Breaks')
262
+ self.attributes[:deb_conflicts] = fields.delete('Conflicts')
263
+ self.attributes[:deb_provides] = fields.delete('Provides')
264
+ self.attributes[:deb_replaces] = fields.delete('Replaces')
265
+
266
+ self.attributes[:deb_field] = Hash[fields.map { |k, v|
267
+ [k.sub(/\AX[BCS]{0,3}-/, ''), v]
268
+ }]
269
+ end # def extract_info
270
+
271
+ def apply_file_info(file)
272
+ self.size = File.size(file)
273
+ self.sha1 = Digest::SHA1.file(file).hexdigest
274
+ self.sha256 = Digest::SHA2.file(file).hexdigest
275
+ self.md5 = Digest::MD5.file(file).hexdigest
276
+ end
277
+
278
+ def parse_control(control)
279
+ field = nil
280
+ value = ""
281
+ {}.tap do |fields|
282
+ control.each_line do |line|
283
+ if line =~ /^(\s+)(\S.*)$/
284
+ indent, rest = $1, $2
285
+ # Continuation
286
+ if indent.size == 1 && rest == "."
287
+ value << "\n"
288
+ rest = ""
289
+ elsif value.size > 0
290
+ value << "\n"
291
+ end
292
+ value << rest
293
+ elsif line =~ /^([-\w]+):(.*)$/
294
+ fields[field] = value if field
295
+ field, value = $1, $2.strip
296
+ end
297
+ end
298
+ fields[field] = value if field
299
+ end
300
+ end
301
+ end