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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/deb/s3/cli.rb +8 -8
  3. data/lib/deb/s3/lock.rb +68 -101
  4. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 180e005ba32e429abe5a462bb81cd03d6897b02fb10e079a655008589fa687df
4
- data.tar.gz: df8aeb29f17894bdfca5f82caaaf5ad6576d07de0fed3789d532de3c93486a13
3
+ metadata.gz: d97ea5c8f4e6115e00a4d33b949a758f762a736be13fda8414d31403149c4314
4
+ data.tar.gz: 5fbac88688aab77391886f77e0d982accc8dd6b79175f649b1033606ebc7b147
5
5
  SHA512:
6
- metadata.gz: '09eaff5677bd51d239794dd5b5bcf9f9ac18275593cd51088f22a0bded0bdd1c2bc4c82cf771aaa8c32d7fd525c973919ac6a5fe4b85818923922e576fa75b13'
7
- data.tar.gz: c463aba5d27113100687654aebd7952ca32142f2835190038dfaf401918d004cdf9bdca74e641866b4e90e5ec91cacbd94bde28c504d0ea637c798181d35e9aa
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], component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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], to_component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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], component, options[:arch], options[:cache_control])
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
- # -*- encoding : utf-8 -*-
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
- attr_reader :user
11
- attr_reader :host
6
+ DYNAMODB_TABLE_NAME = 'deb-s3-lock'
12
7
 
13
- def initialize(user, host)
8
+ def initialize(user, host_with_uuid)
14
9
  @user = user
15
- @host = host
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
- lockbody = "#{Etc.getlogin}@#{Socket.gethostname}-#{uuid}"
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 = [(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).")
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,
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
- 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
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
- # TODO: throw appropriate error class
91
- raise("Unable to obtain a lock after #{max_attempts}, giving up.")
60
+
61
+ raise "Unable to obtain a lock after #{max_attempts}, giving up."
92
62
  end
93
63
 
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))
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 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)
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
- lock = Deb::S3::Lock.new("unknown", "unknown")
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
- 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
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.fix1
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-13 00:00:00.000000000 Z
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