deb-s3-lock-fix 0.11.8.fix1 → 0.11.8.fix3
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/lib/deb/s3/cli.rb +8 -8
- data/lib/deb/s3/lock.rb +68 -101
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d97ea5c8f4e6115e00a4d33b949a758f762a736be13fda8414d31403149c4314
|
4
|
+
data.tar.gz: 5fbac88688aab77391886f77e0d982accc8dd6b79175f649b1033606ebc7b147
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7338af6905ae2c6308e171e1327307e65676e1191e69aeca9ba0dd2fb61665f7fd52d2fd8894d85a51eefadcdf854f85e198cd45e114df1e172cdad968f92b8c
|
7
|
+
data.tar.gz: 19f5b70e1c0c3674c8188bdb881448beb7f5414385054fe9cdd8a2c6ec65581ce5387a487cbe722217851bd9d5e6300f9bd5a1e63e65eaceee24cbd0d6fca148
|
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[:codename]
|
168
|
+
Deb::S3::Lock.lock(options[:codename])
|
169
169
|
@lock_acquired = true
|
170
170
|
end
|
171
171
|
|
@@ -253,7 +253,7 @@ class Deb::S3::CLI < Thor
|
|
253
253
|
log("Update complete.")
|
254
254
|
ensure
|
255
255
|
if options[:lock] && @lock_acquired
|
256
|
-
Deb::S3::Lock.unlock(options[:codename]
|
256
|
+
Deb::S3::Lock.unlock(options[:codename])
|
257
257
|
log("Lock released.")
|
258
258
|
end
|
259
259
|
end
|
@@ -402,7 +402,7 @@ class Deb::S3::CLI < Thor
|
|
402
402
|
if options[:lock]
|
403
403
|
log("Checking for existing lock file")
|
404
404
|
log("Locking repository for updates")
|
405
|
-
Deb::S3::Lock.lock(options[:codename]
|
405
|
+
Deb::S3::Lock.lock(options[:codename])
|
406
406
|
@lock_acquired = true
|
407
407
|
end
|
408
408
|
|
@@ -444,7 +444,7 @@ class Deb::S3::CLI < Thor
|
|
444
444
|
log "Copy complete."
|
445
445
|
ensure
|
446
446
|
if options[:lock] && @lock_acquired
|
447
|
-
Deb::S3::Lock.unlock(options[:codename]
|
447
|
+
Deb::S3::Lock.unlock(options[:codename])
|
448
448
|
log("Lock released.")
|
449
449
|
end
|
450
450
|
end
|
@@ -497,7 +497,7 @@ class Deb::S3::CLI < Thor
|
|
497
497
|
if options[:lock]
|
498
498
|
log("Checking for existing lock file")
|
499
499
|
log("Locking repository for updates")
|
500
|
-
Deb::S3::Lock.lock(options[:codename]
|
500
|
+
Deb::S3::Lock.lock(options[:codename])
|
501
501
|
@lock_acquired = true
|
502
502
|
end
|
503
503
|
|
@@ -545,7 +545,7 @@ class Deb::S3::CLI < Thor
|
|
545
545
|
end
|
546
546
|
ensure
|
547
547
|
if options[:lock] && @lock_acquired
|
548
|
-
Deb::S3::Lock.unlock(options[:codename]
|
548
|
+
Deb::S3::Lock.unlock(options[:codename])
|
549
549
|
log("Lock released.")
|
550
550
|
end
|
551
551
|
end
|
@@ -611,7 +611,7 @@ class Deb::S3::CLI < Thor
|
|
611
611
|
if options[:lock]
|
612
612
|
log("Checking for existing lock file")
|
613
613
|
log("Locking repository for updates")
|
614
|
-
Deb::S3::Lock.lock(options[:codename]
|
614
|
+
Deb::S3::Lock.lock(options[:codename])
|
615
615
|
@lock_acquired = true
|
616
616
|
end
|
617
617
|
|
@@ -675,7 +675,7 @@ class Deb::S3::CLI < Thor
|
|
675
675
|
end
|
676
676
|
ensure
|
677
677
|
if options[:lock] && @lock_acquired
|
678
|
-
Deb::S3::Lock.unlock(options[:codename]
|
678
|
+
Deb::S3::Lock.unlock(options[:codename])
|
679
679
|
log("Lock released.")
|
680
680
|
end
|
681
681
|
end
|
data/lib/deb/s3/lock.rb
CHANGED
@@ -1,125 +1,92 @@
|
|
1
|
-
|
2
|
-
require "base64"
|
3
|
-
require "digest/md5"
|
4
|
-
require "etc"
|
5
|
-
require "socket"
|
6
|
-
require "tempfile"
|
1
|
+
require "aws-sdk-dynamodb"
|
7
2
|
require "securerandom"
|
3
|
+
require "etc"
|
8
4
|
|
9
5
|
class Deb::S3::Lock
|
10
|
-
|
11
|
-
attr_reader :host
|
6
|
+
DYNAMODB_TABLE_NAME = 'deb-s3-lock'
|
12
7
|
|
13
|
-
def initialize(user,
|
8
|
+
def initialize(user, host_with_uuid)
|
14
9
|
@user = user
|
15
|
-
@
|
10
|
+
@host_with_uuid = host_with_uuid
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.dynamodb
|
14
|
+
@dynamodb ||= begin
|
15
|
+
validate_environment_variables!
|
16
|
+
Aws::DynamoDB::Client.new(
|
17
|
+
access_key_id: ENV['DEB_S3_LOCK_ACCESS_KEY_ID'],
|
18
|
+
secret_access_key: ENV['DEB_S3_LOCK_SECRET_ACCESS_KEY'],
|
19
|
+
region: ENV['AWS_REGION']
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.validate_environment_variables!
|
25
|
+
%w[DEB_S3_LOCK_ACCESS_KEY_ID DEB_S3_LOCK_SECRET_ACCESS_KEY AWS_REGION].each do |var|
|
26
|
+
raise "Environment variable #{var} not set." unless ENV[var]
|
27
|
+
end
|
16
28
|
end
|
17
29
|
|
18
30
|
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)
|
31
|
+
def lock(codename, max_attempts = 60, max_wait_interval = 10)
|
47
32
|
uuid = SecureRandom.uuid
|
48
|
-
|
49
|
-
|
50
|
-
initial_lockfile = initial_lock_path(codename, component, architecture, cache_control)
|
51
|
-
final_lockfile = lock_path(codename, component, architecture, cache_control)
|
33
|
+
lock_body = "#{Etc.getlogin}@#{Socket.gethostname}-#{uuid}"
|
34
|
+
lock_key = codename
|
52
35
|
|
53
|
-
md5_b64 = Base64.encode64(Digest::MD5.digest(lockbody))
|
54
|
-
md5_hex = Digest::MD5.hexdigest(lockbody)
|
55
36
|
max_attempts.times do |i|
|
56
|
-
wait_interval = [
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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,
|
37
|
+
wait_interval = [2**i, max_wait_interval].min
|
38
|
+
|
39
|
+
begin
|
40
|
+
dynamodb.put_item({
|
41
|
+
table_name: DYNAMODB_TABLE_NAME,
|
42
|
+
item: {
|
43
|
+
'lock_key' => lock_key,
|
44
|
+
'lock_body' => lock_body
|
72
45
|
},
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
$stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).")
|
86
|
-
sleep(wait_interval)
|
87
|
-
end
|
46
|
+
condition_expression: "attribute_not_exists(lock_key)"
|
47
|
+
})
|
48
|
+
return
|
49
|
+
rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
|
50
|
+
lock_holder = current_lock_holder(codename)
|
51
|
+
current_time = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
52
|
+
$stderr.puts("[#{current_time}] Repository is locked by another user: #{lock_holder.user} at host #{lock_holder.host_with_uuid}")
|
53
|
+
$stderr.puts("Attempting to obtain a lock after #{wait_interval} second(s).")
|
54
|
+
sleep(wait_interval)
|
55
|
+
rescue => e
|
56
|
+
$stderr.puts("Unexpected error: #{e.message}")
|
57
|
+
sleep(wait_interval)
|
88
58
|
end
|
89
59
|
end
|
90
|
-
|
91
|
-
raise
|
60
|
+
|
61
|
+
raise "Unable to obtain a lock after #{max_attempts}, giving up."
|
92
62
|
end
|
93
63
|
|
94
|
-
def unlock(codename
|
95
|
-
|
96
|
-
|
64
|
+
def unlock(codename)
|
65
|
+
dynamodb.delete_item({
|
66
|
+
table_name: DYNAMODB_TABLE_NAME,
|
67
|
+
key: {
|
68
|
+
'lock_key' => codename
|
69
|
+
}
|
70
|
+
})
|
97
71
|
end
|
98
72
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
73
|
+
def current_lock_holder(codename)
|
74
|
+
response = dynamodb.get_item({
|
75
|
+
table_name: DYNAMODB_TABLE_NAME,
|
76
|
+
key: {
|
77
|
+
'lock_key' => codename
|
78
|
+
}
|
79
|
+
})
|
80
|
+
|
81
|
+
if response.item
|
82
|
+
lockdata = response.item['lock_body']
|
83
|
+
user, host_with_uuid = lockdata.split("@", 2)
|
84
|
+
Deb::S3::Lock.new(user, host_with_uuid)
|
104
85
|
else
|
105
|
-
|
86
|
+
Deb::S3::Lock.new("unknown", "unknown")
|
106
87
|
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
88
|
end
|
114
89
|
|
115
|
-
|
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
|
90
|
+
private :dynamodb, :validate_environment_variables!
|
124
91
|
end
|
125
92
|
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.fix3
|
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-09-
|
11
|
+
date: 2023-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -38,6 +38,20 @@ 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'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: minitest
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|