deb-s3-x 0.7.1.1

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,125 @@
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
+
15
+ attr_accessor :files
16
+
17
+ attr_reader :packages
18
+ attr_reader :packages_to_be_upload
19
+
20
+ def initialize
21
+ @packages = []
22
+ @packages_to_be_upload = []
23
+ @component = nil
24
+ @architecture = nil
25
+ @files = {}
26
+ @cache_control = ""
27
+ end
28
+
29
+ class << self
30
+ def retrieve(codename, component, architecture, cache_control)
31
+ m = if s = Deb::S3::Utils.s3_read("dists/#{codename}/#{component}/binary-#{architecture}/Packages")
32
+ self.parse_packages(s)
33
+ else
34
+ self.new
35
+ end
36
+
37
+ m.codename = codename
38
+ m.component = component
39
+ m.architecture = architecture
40
+ m.cache_control = cache_control
41
+ m
42
+ end
43
+
44
+ def parse_packages(str)
45
+ m = self.new
46
+ str.split("\n\n").each do |s|
47
+ next if s.chomp.empty?
48
+ m.packages << Deb::S3::Package.parse_string(s)
49
+ end
50
+ m
51
+ end
52
+ end
53
+
54
+ def add(pkg, preserve_versions, needs_uploading=true)
55
+ if preserve_versions
56
+ packages.delete_if { |p| p.name == pkg.name && p.full_version == pkg.full_version }
57
+ else
58
+ packages.delete_if { |p| p.name == pkg.name }
59
+ end
60
+ packages << pkg
61
+ packages_to_be_upload << pkg if needs_uploading
62
+ pkg
63
+ end
64
+
65
+ def delete_package(pkg, versions=nil)
66
+ deleted = []
67
+ new_packages = @packages.select { |p|
68
+ # Include packages we didn't name
69
+ if p.name != pkg
70
+ p
71
+ # Also include the packages not matching a specified version
72
+ elsif (!versions.nil? and p.name == pkg and !versions.include?(p.version) and !versions.include?("#{p.version}-#{p.iteration}"))
73
+ p
74
+ end
75
+ }
76
+ deleted = @packages - new_packages
77
+ @packages = new_packages
78
+ deleted
79
+ end
80
+
81
+ def generate
82
+ @packages.collect { |pkg| pkg.generate }.join("\n")
83
+ end
84
+
85
+ def write_to_s3
86
+ manifest = self.generate
87
+
88
+ # store any packages that need to be stored
89
+ @packages_to_be_upload.each do |pkg|
90
+ yield pkg.url_filename if block_given?
91
+ s3_store(pkg.filename, pkg.url_filename, 'application/octet-stream; charset=binary')
92
+ end
93
+
94
+ # generate the Packages file
95
+ pkgs_temp = Tempfile.new("Packages")
96
+ pkgs_temp.write manifest
97
+ pkgs_temp.close
98
+ f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages"
99
+ yield f if block_given?
100
+ s3_store(pkgs_temp.path, f, 'binary/octet-stream; charset=binary', self.cache_control)
101
+ @files["#{@component}/binary-#{@architecture}/Packages"] = hashfile(pkgs_temp.path)
102
+ pkgs_temp.unlink
103
+
104
+ # generate the Packages.gz file
105
+ gztemp = Tempfile.new("Packages.gz")
106
+ gztemp.close
107
+ Zlib::GzipWriter.open(gztemp.path) { |gz| gz.write manifest }
108
+ f = "dists/#{@codename}/#{@component}/binary-#{@architecture}/Packages.gz"
109
+ yield f if block_given?
110
+ s3_store(gztemp.path, f, 'application/x-gzip; charset=binary', self.cache_control)
111
+ @files["#{@component}/binary-#{@architecture}/Packages.gz"] = hashfile(gztemp.path)
112
+ gztemp.unlink
113
+
114
+ nil
115
+ end
116
+
117
+ def hashfile(path)
118
+ {
119
+ :size => File.size(path),
120
+ :sha1 => Digest::SHA1.file(path).hexdigest,
121
+ :sha256 => Digest::SHA2.file(path).hexdigest,
122
+ :md5 => Digest::MD5.file(path).hexdigest
123
+ }
124
+ end
125
+ end
@@ -0,0 +1,303 @@
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")
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
131
+ @url_filename || "pool/#{self.name[0]}/#{self.name[0..1]}/#{File.basename(self.filename)}"
132
+ end
133
+
134
+ def url_filename_encoded
135
+ @url_filename || "pool/#{self.name[0]}/#{self.name[0..1]}/#{s3_escape(File.basename(self.filename))}"
136
+ end
137
+
138
+ def generate
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
+
270
+ end # def extract_info
271
+
272
+ def apply_file_info(file)
273
+ self.size = File.size(file)
274
+ self.sha1 = Digest::SHA1.file(file).hexdigest
275
+ self.sha256 = Digest::SHA2.file(file).hexdigest
276
+ self.md5 = Digest::MD5.file(file).hexdigest
277
+ end
278
+
279
+ def parse_control(control)
280
+ field = nil
281
+ value = ""
282
+ {}.tap do |fields|
283
+ control.each_line do |line|
284
+ if line =~ /^(\s+)(\S.*)$/
285
+ indent, rest = $1, $2
286
+ # Continuation
287
+ if indent.size == 1 && rest == "."
288
+ value << "\n"
289
+ rest = ""
290
+ elsif value.size > 0
291
+ value << "\n"
292
+ end
293
+ value << rest
294
+ elsif line =~ /^([-\w]+):(.*)$/
295
+ fields[field] = value if field
296
+ field, value = $1, $2.strip
297
+ end
298
+ end
299
+ fields[field] = value if field
300
+ end
301
+ end
302
+
303
+ end
@@ -0,0 +1,151 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require "tempfile"
3
+
4
+ class Deb::S3::Release
5
+ include Deb::S3::Utils
6
+
7
+ attr_accessor :codename
8
+ attr_accessor :origin
9
+ attr_accessor :suite
10
+ attr_accessor :architectures
11
+ attr_accessor :components
12
+ attr_accessor :cache_control
13
+
14
+ attr_accessor :files
15
+ attr_accessor :policy
16
+
17
+ def initialize
18
+ @origin = nil
19
+ @suite = nil
20
+ @codename = nil
21
+ @architectures = []
22
+ @components = []
23
+ @cache_control = ""
24
+ @files = {}
25
+ @policy = :public_read
26
+ end
27
+
28
+ class << self
29
+ def retrieve(codename, origin=nil, suite=nil, cache_control=nil)
30
+ if s = Deb::S3::Utils.s3_read("dists/#{codename}/Release")
31
+ rel = self.parse_release(s)
32
+ else
33
+ rel = self.new
34
+ rel.codename = codename
35
+ rel.origin = origin unless origin.nil?
36
+ rel.suite = suite unless suite.nil?
37
+ end
38
+ rel.cache_control = cache_control
39
+ rel
40
+ end
41
+
42
+ def parse_release(str)
43
+ rel = self.new
44
+ rel.parse(str)
45
+ rel
46
+ end
47
+ end
48
+
49
+ def filename
50
+ "dists/#{@codename}/Release"
51
+ end
52
+
53
+ def parse(str)
54
+ parse = lambda do |field|
55
+ value = str[/^#{field}: .*/]
56
+ if value.nil?
57
+ return nil
58
+ else
59
+ return value.split(": ",2).last
60
+ end
61
+ end
62
+
63
+ # grab basic fields
64
+ self.codename = parse.call("Codename")
65
+ self.origin = parse.call("Origin") || nil
66
+ self.suite = parse.call("Suite") || nil
67
+ self.architectures = (parse.call("Architectures") || "").split(/\s+/)
68
+ self.components = (parse.call("Components") || "").split(/\s+/)
69
+
70
+ # find all the hashes
71
+ str.scan(/^\s+([^\s]+)\s+(\d+)\s+(.+)$/).each do |(hash,size,name)|
72
+ self.files[name] ||= { :size => size.to_i }
73
+ case hash.length
74
+ when 32
75
+ self.files[name][:md5] = hash
76
+ when 40
77
+ self.files[name][:sha1] = hash
78
+ when 64
79
+ self.files[name][:sha256] = hash
80
+ end
81
+ end
82
+ end
83
+
84
+ def generate
85
+ template("release.erb").result(binding)
86
+ end
87
+
88
+ def write_to_s3
89
+ # validate some other files are present
90
+ if block_given?
91
+ self.validate_others { |f| yield f }
92
+ else
93
+ self.validate_others
94
+ end
95
+
96
+ # generate the Release file
97
+ release_tmp = Tempfile.new("Release")
98
+ release_tmp.puts self.generate
99
+ release_tmp.close
100
+ yield self.filename if block_given?
101
+ s3_store(release_tmp.path, self.filename, 'binary/octet-stream; charset=binary', self.cache_control)
102
+
103
+ # sign the file, if necessary
104
+ if Deb::S3::Utils.signing_key
105
+ key_param = Deb::S3::Utils.signing_key != "" ? "--default-key=#{Deb::S3::Utils.signing_key}" : ""
106
+ if system("gpg -a #{key_param} #{Deb::S3::Utils.gpg_options} -b #{release_tmp.path}")
107
+ local_file = release_tmp.path+".asc"
108
+ remote_file = self.filename+".gpg"
109
+ yield remote_file if block_given?
110
+ raise "Unable to locate Release signature file" unless File.exists?(local_file)
111
+ s3_store(local_file, remote_file, 'application/pgp-signature; charset=us-ascii', self.cache_control)
112
+ File.unlink(local_file)
113
+ else
114
+ raise "Signing the Release file failed."
115
+ end
116
+ else
117
+ # remove an existing Release.gpg, if it was there
118
+ s3_remove(self.filename+".gpg")
119
+ end
120
+
121
+ release_tmp.unlink
122
+ end
123
+
124
+ def update_manifest(manifest)
125
+ self.components << manifest.component unless self.components.include?(manifest.component)
126
+ self.architectures << manifest.architecture unless self.architectures.include?(manifest.architecture)
127
+ self.files.merge!(manifest.files)
128
+ end
129
+
130
+ def validate_others
131
+ to_apply = []
132
+ self.components.each do |comp|
133
+ %w(amd64 i386).each do |arch|
134
+ next if self.files.has_key?("#{comp}/binary-#{arch}/Packages")
135
+
136
+ m = Deb::S3::Manifest.new
137
+ m.codename = self.codename
138
+ m.component = comp
139
+ m.architecture = arch
140
+ if block_given?
141
+ m.write_to_s3 { |f| yield f }
142
+ else
143
+ m.write_to_s3
144
+ end
145
+ to_apply << m
146
+ end
147
+ end
148
+
149
+ to_apply.each { |m| self.update_manifest(m) }
150
+ end
151
+ end