deb-s3-lock-fix 0.11.8.fix0.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,96 @@
1
+ require "aws-sdk-dynamodb"
2
+ require "securerandom"
3
+ require "etc"
4
+ require "time"
5
+
6
+ class Deb::S3::Lock
7
+ attr_reader :user, :host_with_uuid
8
+
9
+ DYNAMODB_TABLE_NAME = 'deb-s3-lock'
10
+
11
+ def initialize(user, host_with_uuid)
12
+ @user = user
13
+ @host_with_uuid = host_with_uuid
14
+ end
15
+
16
+ def self.dynamodb
17
+ @dynamodb ||= begin
18
+ validate_environment_variables!
19
+ Aws::DynamoDB::Client.new(
20
+ access_key_id: ENV['DEB_S3_ACCESS_KEY_ID'],
21
+ secret_access_key: ENV['DEB_S3_SECRET_ACCESS_KEY'],
22
+ region: ENV['AWS_BUILDERS_REGION']
23
+ )
24
+ end
25
+ end
26
+
27
+ def self.validate_environment_variables!
28
+ %w[DEB_S3_LOCK_ACCESS_KEY_ID DEB_S3_LOCK_SECRET_ACCESS_KEY AWS_BUILDERS_REGION].each do |var|
29
+ raise "Environment variable #{var} not set." unless ENV[var]
30
+ end
31
+ end
32
+
33
+ class << self
34
+ def lock(codename, max_attempts = 60, max_wait_interval = 10)
35
+ uuid = SecureRandom.uuid
36
+ lock_body = "#{Etc.getlogin}@#{Socket.gethostname}-#{uuid}"
37
+ lock_key = codename
38
+
39
+ $stderr.puts("Current job's hostname with UUID: #{lock_body}")
40
+
41
+ max_attempts.times do |i|
42
+ wait_interval = [2**i, max_wait_interval].min
43
+
44
+ expiration_time = Time.now.to_i + 45
45
+ begin
46
+ dynamodb.put_item({
47
+ table_name: DYNAMODB_TABLE_NAME,
48
+ item: {
49
+ 'lock_key' => "52",
50
+ 'lock_body' => lock_body,
51
+ 'ttl' => expiration_time
52
+ },
53
+ condition_expression: "attribute_not_exists(lock_key)"
54
+ })
55
+ return
56
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
57
+ lock_holder = current_lock_holder(codename)
58
+ current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
59
+ $stderr.puts("[#{current_time}] Repository is locked by another user: #{lock_holder.user} at host #{lock_holder.host_with_uuid}")
60
+ $stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).")
61
+ sleep(wait_interval)
62
+ end
63
+ end
64
+
65
+ raise "Unable to obtain a lock after #{max_attempts} attemtps, giving up."
66
+ end
67
+
68
+ def unlock(codename)
69
+ # dynamodb.delete_item({
70
+ # table_name: DYNAMODB_TABLE_NAME,
71
+ # key: {
72
+ # 'lock_key' => codename
73
+ # }
74
+ # })
75
+ end
76
+
77
+ def current_lock_holder(codename)
78
+ response = dynamodb.get_item({
79
+ table_name: DYNAMODB_TABLE_NAME,
80
+ key: {
81
+ 'lock_key' => codename
82
+ }
83
+ })
84
+
85
+ if response.item
86
+ lockdata = response.item['lock_body']
87
+ user, host_with_uuid = lockdata.split("@", 2)
88
+ Deb::S3::Lock.new(user, host_with_uuid)
89
+ else
90
+ Deb::S3::Lock.new("unknown", "unknown")
91
+ end
92
+ end
93
+
94
+ private :dynamodb, :validate_environment_variables!
95
+ end
96
+ 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
@@ -0,0 +1,161 @@
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
+ end
35
+ rel.codename = codename
36
+ rel.origin = origin unless origin.nil?
37
+ rel.suite = suite unless suite.nil?
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, 'text/plain; charset=utf-8', 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.any? ? "-u #{Deb::S3::Utils.signing_key.join(" -u ")}" : ""
106
+ if system("gpg -a #{key_param} --digest-algo SHA256 #{Deb::S3::Utils.gpg_options} -s --clearsign #{release_tmp.path}")
107
+ local_file = release_tmp.path+".asc"
108
+ remote_file = "dists/#{@codename}/InRelease"
109
+ yield remote_file if block_given?
110
+ raise "Unable to locate InRelease file" unless File.exist?(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 InRelease file failed."
115
+ end
116
+ if system("gpg -a #{key_param} --digest-algo SHA256 #{Deb::S3::Utils.gpg_options} -b #{release_tmp.path}")
117
+ local_file = release_tmp.path+".asc"
118
+ remote_file = self.filename+".gpg"
119
+ yield remote_file if block_given?
120
+ raise "Unable to locate Release signature file" unless File.exist?(local_file)
121
+ s3_store(local_file, remote_file, 'application/pgp-signature; charset=us-ascii', self.cache_control)
122
+ File.unlink(local_file)
123
+ else
124
+ raise "Signing the Release file failed."
125
+ end
126
+ else
127
+ # remove an existing Release.gpg, if it was there
128
+ s3_remove(self.filename+".gpg")
129
+ end
130
+
131
+ release_tmp.unlink
132
+ end
133
+
134
+ def update_manifest(manifest)
135
+ self.components << manifest.component unless self.components.include?(manifest.component)
136
+ self.architectures << manifest.architecture unless self.architectures.include?(manifest.architecture)
137
+ self.files.merge!(manifest.files)
138
+ end
139
+
140
+ def validate_others
141
+ to_apply = []
142
+ self.components.each do |comp|
143
+ %w(amd64 i386 armhf).each do |arch|
144
+ next if self.files.has_key?("#{comp}/binary-#{arch}/Packages")
145
+
146
+ m = Deb::S3::Manifest.new
147
+ m.codename = self.codename
148
+ m.component = comp
149
+ m.architecture = arch
150
+ if block_given?
151
+ m.write_to_s3 { |f| yield f }
152
+ else
153
+ m.write_to_s3
154
+ end
155
+ to_apply << m
156
+ end
157
+ end
158
+
159
+ to_apply.each { |m| self.update_manifest(m) }
160
+ end
161
+ end