deb-s3-lock-fix 0.11.8.fix0.6 → 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 +4 -4
- data/README.md +2 -12
- data/lib/deb/s3/cli.rb +10 -14
- data/lib/deb/s3/lock.rb +98 -112
- metadata +7 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 180e005ba32e429abe5a462bb81cd03d6897b02fb10e079a655008589fa687df
|
4
|
+
data.tar.gz: df8aeb29f17894bdfca5f82caaaf5ad6576d07de0fed3789d532de3c93486a13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09eaff5677bd51d239794dd5b5bcf9f9ac18275593cd51088f22a0bded0bdd1c2bc4c82cf771aaa8c32d7fd525c973919ac6a5fe4b85818923922e576fa75b13'
|
7
|
+
data.tar.gz: c463aba5d27113100687654aebd7952ca32142f2835190038dfaf401918d004cdf9bdca74e641866b4e90e5ec91cacbd94bde28c504d0ea637c798181d35e9aa
|
data/README.md
CHANGED
@@ -1,18 +1,8 @@
|
|
1
|
-
# deb-s3
|
1
|
+
# deb-s3
|
2
2
|
|
3
3
|
[](https://travis-ci.org/deb-s3/deb-s3)
|
4
4
|
|
5
|
-
**This repository is a fork of [deb-s3](https://github.com/
|
6
|
-
|
7
|
-
Note: The locking mechanism in the original deb-s3 library does not prevent race conditions.
|
8
|
-
It relies on S3 for distributed locking, which will not work consistently due to S3's eventual
|
9
|
-
consistency model. This fork uses DynamoDB for distributed locking, since it ensures atomic
|
10
|
-
conditional put operations on the lock.
|
11
|
-
|
12
|
-
To use this library create a DynamoDB table and export the following three env variables:
|
13
|
-
`DEB_S3_ACCESS_KEY_ID`
|
14
|
-
`DEB_S3_SECRET_ACCESS_KEY`
|
15
|
-
`AWS_BUILDERS_REGION`
|
5
|
+
**This repository is a fork of [krobertson/deb-s3](https://github.com/krobertson/deb-s3).**
|
16
6
|
|
17
7
|
`deb-s3` is a simple utility to make creating and managing APT repositories on
|
18
8
|
S3.
|
data/lib/deb/s3/cli.rb
CHANGED
@@ -165,7 +165,7 @@ class Deb::S3::CLI < Thor
|
|
165
165
|
if options[:lock]
|
166
166
|
log("Checking for existing lock file")
|
167
167
|
log("Locking repository for updates")
|
168
|
-
Deb::S3::Lock.lock(options[:
|
168
|
+
Deb::S3::Lock.lock(options[:codename], component, options[:arch], options[:cache_control])
|
169
169
|
@lock_acquired = true
|
170
170
|
end
|
171
171
|
|
@@ -242,22 +242,18 @@ class Deb::S3::CLI < Thor
|
|
242
242
|
log("Uploading packages and new manifests to S3")
|
243
243
|
manifests.each_value do |manifest|
|
244
244
|
begin
|
245
|
-
manifest.write_to_s3
|
246
|
-
sublog("Transferring #{f} at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
|
247
|
-
end
|
245
|
+
manifest.write_to_s3 { |f| sublog("Transferring #{f}") }
|
248
246
|
rescue Deb::S3::Utils::AlreadyExistsError => e
|
249
247
|
error("Uploading manifest failed because: #{e}")
|
250
248
|
end
|
251
249
|
release.update_manifest(manifest)
|
252
250
|
end
|
253
|
-
release.write_to_s3
|
254
|
-
sublog("Transferring #{f} at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
|
255
|
-
end
|
251
|
+
release.write_to_s3 { |f| sublog("Transferring #{f}") }
|
256
252
|
|
257
253
|
log("Update complete.")
|
258
254
|
ensure
|
259
255
|
if options[:lock] && @lock_acquired
|
260
|
-
Deb::S3::Lock.unlock(options[:
|
256
|
+
Deb::S3::Lock.unlock(options[:codename], component, options[:arch], options[:cache_control])
|
261
257
|
log("Lock released.")
|
262
258
|
end
|
263
259
|
end
|
@@ -406,7 +402,7 @@ class Deb::S3::CLI < Thor
|
|
406
402
|
if options[:lock]
|
407
403
|
log("Checking for existing lock file")
|
408
404
|
log("Locking repository for updates")
|
409
|
-
Deb::S3::Lock.lock(options[:
|
405
|
+
Deb::S3::Lock.lock(options[:codename], to_component, options[:arch], options[:cache_control])
|
410
406
|
@lock_acquired = true
|
411
407
|
end
|
412
408
|
|
@@ -448,7 +444,7 @@ class Deb::S3::CLI < Thor
|
|
448
444
|
log "Copy complete."
|
449
445
|
ensure
|
450
446
|
if options[:lock] && @lock_acquired
|
451
|
-
Deb::S3::Lock.unlock(options[:
|
447
|
+
Deb::S3::Lock.unlock(options[:codename], component, options[:arch], options[:cache_control])
|
452
448
|
log("Lock released.")
|
453
449
|
end
|
454
450
|
end
|
@@ -501,7 +497,7 @@ class Deb::S3::CLI < Thor
|
|
501
497
|
if options[:lock]
|
502
498
|
log("Checking for existing lock file")
|
503
499
|
log("Locking repository for updates")
|
504
|
-
Deb::S3::Lock.lock(options[:
|
500
|
+
Deb::S3::Lock.lock(options[:codename], component, options[:arch], options[:cache_control])
|
505
501
|
@lock_acquired = true
|
506
502
|
end
|
507
503
|
|
@@ -549,7 +545,7 @@ class Deb::S3::CLI < Thor
|
|
549
545
|
end
|
550
546
|
ensure
|
551
547
|
if options[:lock] && @lock_acquired
|
552
|
-
Deb::S3::Lock.unlock(options[:
|
548
|
+
Deb::S3::Lock.unlock(options[:codename], component, options[:arch], options[:cache_control])
|
553
549
|
log("Lock released.")
|
554
550
|
end
|
555
551
|
end
|
@@ -615,7 +611,7 @@ class Deb::S3::CLI < Thor
|
|
615
611
|
if options[:lock]
|
616
612
|
log("Checking for existing lock file")
|
617
613
|
log("Locking repository for updates")
|
618
|
-
Deb::S3::Lock.lock(options[:
|
614
|
+
Deb::S3::Lock.lock(options[:codename], component, options[:arch], options[:cache_control])
|
619
615
|
@lock_acquired = true
|
620
616
|
end
|
621
617
|
|
@@ -679,7 +675,7 @@ class Deb::S3::CLI < Thor
|
|
679
675
|
end
|
680
676
|
ensure
|
681
677
|
if options[:lock] && @lock_acquired
|
682
|
-
Deb::S3::Lock.unlock(options[:
|
678
|
+
Deb::S3::Lock.unlock(options[:codename], component, options[:arch], options[:cache_control])
|
683
679
|
log("Lock released.")
|
684
680
|
end
|
685
681
|
end
|
data/lib/deb/s3/lock.rb
CHANGED
@@ -1,139 +1,125 @@
|
|
1
|
-
|
2
|
-
require "
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require "base64"
|
3
|
+
require "digest/md5"
|
3
4
|
require "etc"
|
4
|
-
require "
|
5
|
+
require "socket"
|
6
|
+
require "tempfile"
|
7
|
+
require "securerandom"
|
5
8
|
|
6
9
|
class Deb::S3::Lock
|
7
|
-
attr_reader :user
|
8
|
-
|
9
|
-
DYNAMODB_TABLE_NAME = 'deb-s3-lock'
|
10
|
+
attr_reader :user
|
11
|
+
attr_reader :host
|
10
12
|
|
11
|
-
def initialize(user,
|
13
|
+
def initialize(user, host)
|
12
14
|
@user = user
|
13
|
-
@
|
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_ACCESS_KEY_ID DEB_S3_SECRET_ACCESS_KEY AWS_BUILDERS_REGION].each do |var|
|
29
|
-
raise "Environment variable #{var} not set." unless ENV[var]
|
30
|
-
end
|
15
|
+
@host = host
|
31
16
|
end
|
32
17
|
|
33
18
|
class << self
|
34
|
-
|
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)
|
35
47
|
uuid = SecureRandom.uuid
|
36
|
-
|
37
|
-
lock_key = "#{bucket}/#{codename}"
|
48
|
+
lockbody = "#{Etc.getlogin}@#{Socket.gethostname}-#{uuid}"
|
38
49
|
|
39
|
-
|
50
|
+
initial_lockfile = initial_lock_path(codename, component, architecture, cache_control)
|
51
|
+
final_lockfile = lock_path(codename, component, architecture, cache_control)
|
40
52
|
|
53
|
+
md5_b64 = Base64.encode64(Digest::MD5.digest(lockbody))
|
54
|
+
md5_hex = Digest::MD5.hexdigest(lockbody)
|
41
55
|
max_attempts.times do |i|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
dynamodb.put_item({
|
48
|
-
table_name: DYNAMODB_TABLE_NAME,
|
49
|
-
item: {
|
50
|
-
'lock_key' => "52",
|
51
|
-
'lock_body' => lock_body,
|
52
|
-
'ttl' => expiration_time
|
53
|
-
},
|
54
|
-
condition_expression: "attribute_not_exists(lock_key)"
|
55
|
-
})
|
56
|
-
return
|
57
|
-
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
58
|
-
lock_holder = current_lock_holder(bucket, codename)
|
59
|
-
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
60
|
-
$stderr.puts("[#{current_time}] Repository is locked by another user: #{lock_holder.user} at host #{lock_holder.host_with_uuid}")
|
61
|
-
$stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).")
|
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).")
|
62
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
|
63
88
|
end
|
64
89
|
end
|
65
|
-
|
66
|
-
raise
|
90
|
+
# TODO: throw appropriate error class
|
91
|
+
raise("Unable to obtain a lock after #{max_attempts}, giving up.")
|
67
92
|
end
|
68
93
|
|
69
|
-
def unlock(
|
70
|
-
|
71
|
-
|
72
|
-
# key: {
|
73
|
-
# 'lock_key' => "#{bucket}/#{codename}"
|
74
|
-
# }
|
75
|
-
# })
|
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))
|
76
97
|
end
|
77
98
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
}
|
84
|
-
})
|
85
|
-
|
86
|
-
if response.item
|
87
|
-
lockdata = response.item['lock_body']
|
88
|
-
user, host_with_uuid = lockdata.split("@", 2)
|
89
|
-
Deb::S3::Lock.new(user, host_with_uuid)
|
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)
|
90
104
|
else
|
91
|
-
Deb::S3::Lock.new("unknown", "unknown")
|
105
|
+
lock = Deb::S3::Lock.new("unknown", "unknown")
|
92
106
|
end
|
107
|
+
lock
|
93
108
|
end
|
94
109
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
key: {
|
99
|
-
'lock_key' => "52"
|
100
|
-
}
|
101
|
-
})
|
102
|
-
$stderr.puts("hi: #{response.item['ttl']} #{response.item['lock_body']} #{response.item['lock_key']}]")
|
103
|
-
|
104
|
-
return unless response.item
|
105
|
-
|
106
|
-
if response.item['ttl'] && Time.now.to_i > response.item['ttl'].to_i
|
107
|
-
lockdata = response.item['lock_body']
|
108
|
-
user, _ = lockdata.split("@", 2)
|
109
|
-
$stderr.puts("hello")
|
110
|
-
|
111
|
-
delete_response = nil
|
112
|
-
|
113
|
-
begin
|
114
|
-
delete_response = dynamodb.delete_item({
|
115
|
-
table_name: DYNAMODB_TABLE_NAME,
|
116
|
-
key: {
|
117
|
-
'lock_key' => "52"
|
118
|
-
},
|
119
|
-
return_values: 'ALL_OLD'
|
120
|
-
})
|
121
|
-
rescue Aws::DynamoDB::Errors::ServiceError => e
|
122
|
-
$stderr.puts("Error deleting lock: #{e.message}")
|
123
|
-
return
|
124
|
-
end
|
125
|
-
$stderr.puts("Expired lock detected and deleted for #{bucket}/#{codename}. Lock was held by user: #{user}.")
|
126
|
-
|
127
|
-
if delete_response.attributes
|
128
|
-
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
129
|
-
$stderr.puts("[#{current_time}] Expired lock detected and deleted for #{bucket}/#{codename}. Lock was held by user: #{user}.")
|
130
|
-
end
|
131
|
-
end
|
110
|
+
private
|
111
|
+
def initial_lock_path(codename, component = nil, architecture = nil, cache_control = nil)
|
112
|
+
"dists/#{codename}/lockfile.lock"
|
132
113
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
114
|
|
137
|
-
|
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
|
138
124
|
end
|
139
125
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deb-s3-lock-fix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.8.
|
4
|
+
version: 0.11.8.fix1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Braeden Wolf & Ken Robertson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: aws-sdk-dynamodb
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: minitest
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,8 +66,9 @@ dependencies:
|
|
80
66
|
- - "~>"
|
81
67
|
- !ruby/object:Gem::Version
|
82
68
|
version: '11'
|
83
|
-
description: Fork of deb-s3 with fix for locking. Original work by Ken
|
84
|
-
|
69
|
+
description: Fork of deb-s3 with a specific fix for locking. Original work by Ken
|
70
|
+
Robertson.
|
71
|
+
email: braedenwolf@outlook.com & ken@invalidlogic.com
|
85
72
|
executables:
|
86
73
|
- deb-s3
|
87
74
|
extensions: []
|
@@ -98,7 +85,7 @@ files:
|
|
98
85
|
- lib/deb/s3/templates/package.erb
|
99
86
|
- lib/deb/s3/templates/release.erb
|
100
87
|
- lib/deb/s3/utils.rb
|
101
|
-
homepage: https://
|
88
|
+
homepage: https://github.com/braedenwolf/deb-s3-lock-fix
|
102
89
|
licenses:
|
103
90
|
- MIT
|
104
91
|
metadata: {}
|
@@ -120,5 +107,5 @@ requirements: []
|
|
120
107
|
rubygems_version: 3.1.2
|
121
108
|
signing_key:
|
122
109
|
specification_version: 4
|
123
|
-
summary: Easily create and manage an APT repository on S3 with lock fix.
|
110
|
+
summary: Easily create and manage an APT repository on S3 (with specific lock fix).
|
124
111
|
test_files: []
|