gcslock 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/README.md +21 -0
- data/bin/rspec +16 -0
- data/gcslock.gemspec +29 -0
- data/lib/gcslock/errors.rb +7 -0
- data/lib/gcslock/mutex.rb +97 -0
- data/lib/gcslock/version.rb +3 -0
- data/spec/gcslock/mutex_spec.rb +308 -0
- data/spec/spec_helper.rb +9 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1068b280fd4e77bb1270880eaa80aa0145d64f35d58121329e8500ecc2321948
|
4
|
+
data.tar.gz: 33b5f11756a7c89f55384730322fd43aebcc810dff452c4cbf13bb05f2920aea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de5e846755a1c556d263b6af0806b6e102bbd4c474aad7513aa3df8d84d6a8e1afb56b35cc3006e52f76508eac3bc691b5776175142f0e3d1cd9a0fb68ad40fa
|
7
|
+
data.tar.gz: fc5c9318fcdabb46acff6b89b54c9967ed13749c8cda75cc78699d063f233135a126013874667adc4e300c4975d82ad8da0971f1a972aed23ae01fdf236001b7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# gcslock-ruby
|
2
|
+
|
3
|
+
This is inspired by [the Golang version](https://github.com/marcacohen/gcslock).
|
4
|
+
|
5
|
+
## Google Cloud Storage setup
|
6
|
+
|
7
|
+
1. Setup a new project at the [Google APIs Console](https://console.developers.google.com) and enable the Cloud Storage API.
|
8
|
+
1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/downloads) tool and configure your project and your OAuth credentials.
|
9
|
+
1. Create a bucket in which to store your lock file using the command `gsutil mb gs://your-bucket-name`.
|
10
|
+
1. Enable object versioning in your bucket using the command `gsutil versioning set on gs://your-bucket-name`.
|
11
|
+
1. In your Ruby code, require `gcslock/mutex` and use it as follows:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'gcslock/mutex'
|
15
|
+
|
16
|
+
m = GCSLock::Mutex.new('your-bucket-name', 'my-file.lock')
|
17
|
+
m.synchronize do
|
18
|
+
// Protected and globally serialized computation happens here.
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load(Gem.bin_path('rspec-core', 'rspec'))
|
data/gcslock.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'gcslock/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'gcslock'
|
7
|
+
spec.version = GCSLock::VERSION
|
8
|
+
spec.authors = ['Raphaël Beamonte']
|
9
|
+
spec.email = ['raphael.beamonte@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Google Cloud Storage distributed locking'
|
12
|
+
spec.description = "Allows to use a Google Cloud Storage bucket as a distributed locking system"
|
13
|
+
spec.homepage = 'https://github.com/XaF/gcslock-ruby'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes." unless spec.respond_to?(:metadata)
|
17
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.files = %x(git ls-files -z).split("\x0").reject { |f| f.match(/^(test|DESIGN)/) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.required_ruby_version = '>= 2.0'
|
23
|
+
|
24
|
+
spec.add_runtime_dependency 'google-api-client'
|
25
|
+
spec.add_runtime_dependency 'google-cloud-storage', '~> 1.26.1'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'codecov'
|
29
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'google/cloud/storage'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
require_relative 'errors'
|
5
|
+
|
6
|
+
module GCSLock
|
7
|
+
class Mutex
|
8
|
+
def initialize(bucket, object, client: nil, uuid: nil, min_backoff: nil, max_backoff: nil)
|
9
|
+
@client = client || Google::Cloud::Storage.new
|
10
|
+
@bucket = @client.bucket(bucket, skip_lookup: true)
|
11
|
+
@object = @bucket.file(object, skip_lookup: true)
|
12
|
+
|
13
|
+
@uuid = uuid || SecureRandom.uuid
|
14
|
+
@min_backoff = min_backoff || 0.01
|
15
|
+
@max_backoff = max_backoff || 5.0
|
16
|
+
end
|
17
|
+
|
18
|
+
# Attempts to grab the lock and waits if it isn't available.
|
19
|
+
# Raises `ThreadError` if `mutex` was locked by the current thread.
|
20
|
+
def lock(timeout: nil)
|
21
|
+
raise LockAlreadyOwnedError, "Mutex for #{@object.name} is already owned by this process" if owned?
|
22
|
+
|
23
|
+
backoff = @min_backoff
|
24
|
+
waited = 0.0 unless timeout.nil?
|
25
|
+
|
26
|
+
loop do
|
27
|
+
return true if try_lock
|
28
|
+
break if !timeout.nil? && waited + backoff > timeout
|
29
|
+
sleep(backoff)
|
30
|
+
|
31
|
+
backoff_opts = [@max_backoff, backoff * 2]
|
32
|
+
|
33
|
+
unless timeout.nil?
|
34
|
+
waited += backoff
|
35
|
+
backoff_opts.push(timeout - waited) if timeout > waited
|
36
|
+
end
|
37
|
+
|
38
|
+
backoff = backoff_opts.min
|
39
|
+
end
|
40
|
+
|
41
|
+
raise LockTimeoutError, "Unable to get mutex for #{@object.name} before timeout"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns `true` if this lock is currently held by some thread.
|
45
|
+
def locked?
|
46
|
+
@object.reload!
|
47
|
+
@object.exists?
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns `true` if this lock is currently held by current thread.
|
51
|
+
def owned?
|
52
|
+
locked? && @object.size == @uuid.size && @object.download.read == @uuid
|
53
|
+
end
|
54
|
+
|
55
|
+
# Obtains a lock, runs the block, and releases the lock when the block completes.
|
56
|
+
# Raises `LockAlreadyOwnedError` if the lock is already owned by the current instance.
|
57
|
+
def synchronize(timeout: nil)
|
58
|
+
raise LockAlreadyOwnedError, "Mutex for #{@object.name} is already owned by this process" if owned?
|
59
|
+
|
60
|
+
lock(timeout: timeout)
|
61
|
+
begin
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
unlock
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Attempts to obtain the lock and returns immediately. Returns `true` if the lock was granted.
|
69
|
+
def try_lock
|
70
|
+
@client.service.service.insert_object(
|
71
|
+
@bucket.name,
|
72
|
+
name: @object.name,
|
73
|
+
if_generation_match: 0,
|
74
|
+
upload_source: StringIO.new(@uuid),
|
75
|
+
)
|
76
|
+
|
77
|
+
true
|
78
|
+
rescue Google::Apis::ClientError => e
|
79
|
+
raise unless e.status_code == 412 && e.message.start_with?('conditionNotMet:')
|
80
|
+
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
# Releases the lock. Raises `LockNotOwnedError` if the lock is not owned by the current instance.
|
85
|
+
def unlock
|
86
|
+
raise LockNotOwnedError, "Mutex for #{@object.name} is not owned by this process" unless owned?
|
87
|
+
@object.delete
|
88
|
+
end
|
89
|
+
|
90
|
+
# Releases the lock even if not owned by this instance. Raises `LockNotFoundError` if the lock cannot be found.
|
91
|
+
def unlock!
|
92
|
+
@object.delete
|
93
|
+
rescue Google::Cloud::NotFoundError => e
|
94
|
+
raise LockNotFoundError, "Mutex for #{@object.name} not found"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gcslock/mutex'
|
3
|
+
|
4
|
+
describe GCSLock::Mutex do
|
5
|
+
before do
|
6
|
+
@bucket_name = 'bucket'
|
7
|
+
@object_name = 'object'
|
8
|
+
|
9
|
+
@gcs = instance_double(Google::Cloud::Storage::Project)
|
10
|
+
allow(Google::Cloud::Storage).to receive(:new).and_return(@gcs)
|
11
|
+
|
12
|
+
@bucket = instance_double(Google::Cloud::Storage::Bucket)
|
13
|
+
allow(@bucket).to receive(:name).and_return(@bucket_name)
|
14
|
+
allow(@gcs).to receive(:bucket).and_return(@bucket)
|
15
|
+
|
16
|
+
@object = instance_double(Google::Cloud::Storage::File)
|
17
|
+
allow(@object).to receive(:name).and_return(@object_name)
|
18
|
+
allow(@bucket).to receive(:file).and_return(@object)
|
19
|
+
|
20
|
+
@uuid = 'some_uuid'
|
21
|
+
allow(SecureRandom).to receive(:uuid).and_return(@uuid)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.initialize' do
|
25
|
+
before do
|
26
|
+
@gcs = double(Google::Cloud::Storage)
|
27
|
+
allow(Google::Cloud::Storage).to receive(:new).and_return(@gcs)
|
28
|
+
|
29
|
+
@bucket = double(Google::Cloud::Storage::Bucket)
|
30
|
+
allow(@gcs).to receive(:bucket).and_return(@bucket)
|
31
|
+
|
32
|
+
@object = double(Google::Cloud::Storage::File)
|
33
|
+
allow(@bucket).to receive(:file).and_return(@object)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'initializes in GCS client when none provided' do
|
37
|
+
expect(Google::Cloud::Storage).to receive(:new).once
|
38
|
+
|
39
|
+
GCSLock::Mutex.new(@bucket_name, @object_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'initializes in GCS client when none provided' do
|
43
|
+
expect(Google::Cloud::Storage).not_to receive(:new)
|
44
|
+
|
45
|
+
GCSLock::Mutex.new(@bucket_name, @object_name, client: @gcs)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'initializes a bucket with lazy loading' do
|
49
|
+
expect(Google::Cloud::Storage).to receive(:new).once
|
50
|
+
expect(@gcs).to receive(:bucket).with(@bucket_name, skip_lookup: true).once
|
51
|
+
|
52
|
+
GCSLock::Mutex.new(@bucket_name, @object_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'initializes a file with lazy loading' do
|
56
|
+
expect(Google::Cloud::Storage).to receive(:new).once
|
57
|
+
expect(@bucket).to receive(:file).with(@object_name, skip_lookup: true).once
|
58
|
+
|
59
|
+
GCSLock::Mutex.new(@bucket_name, @object_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'initializes a randomly generated unique ID' do
|
63
|
+
expect(SecureRandom).to receive(:uuid).once
|
64
|
+
|
65
|
+
GCSLock::Mutex.new(@bucket_name, @object_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'initialized' do
|
70
|
+
before do
|
71
|
+
@mutex = GCSLock::Mutex.new(@bucket_name, @object_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.lock' do
|
75
|
+
it 'sleeps and retry when failing on the first try_lock' do
|
76
|
+
expect(@mutex).to receive(:owned?).once.and_return(false)
|
77
|
+
expect(@mutex).to receive(:try_lock).once.and_return(false)
|
78
|
+
expect(@mutex).to receive(:sleep).once
|
79
|
+
expect(@mutex).to receive(:try_lock).once.and_return(true)
|
80
|
+
|
81
|
+
@mutex.lock(timeout: 2)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'sleeps just the time needed to retry once at the end' do
|
85
|
+
expect(@mutex).to receive(:owned?).once.and_return(false)
|
86
|
+
expect(@mutex).to receive(:sleep).exactly(2).times
|
87
|
+
expect(@mutex).to receive(:try_lock).exactly(3).times.and_return(false)
|
88
|
+
|
89
|
+
expect do
|
90
|
+
@mutex.lock(timeout: 0.03)
|
91
|
+
end.to raise_error(GCSLock::LockTimeoutError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'raises an error if unable to get the lock when reaching the timeout' do
|
95
|
+
expect(@mutex).to receive(:owned?).once.and_return(false)
|
96
|
+
expect(@mutex).to receive(:try_lock).once.and_return(false)
|
97
|
+
|
98
|
+
expect do
|
99
|
+
@mutex.lock(timeout: 0)
|
100
|
+
end.to raise_error(GCSLock::LockTimeoutError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'raises an error if the lock is already owned' do
|
104
|
+
expect(@mutex).to receive(:owned?).once.and_return(true)
|
105
|
+
expect(@mutex).not_to receive(:try_lock)
|
106
|
+
|
107
|
+
expect do
|
108
|
+
@mutex.lock
|
109
|
+
end.to raise_error(GCSLock::LockAlreadyOwnedError)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '.locked?' do
|
114
|
+
before do
|
115
|
+
allow(@object).to receive(:reload!)
|
116
|
+
allow(@object).to receive(:exists?)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'calls reload! on the lock file' do
|
120
|
+
expect(@object).to receive(:reload!).once
|
121
|
+
|
122
|
+
@mutex.locked?
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'calls exists? on the lock file' do
|
126
|
+
expect(@object).to receive(:exists?).once
|
127
|
+
|
128
|
+
@mutex.locked?
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'returns true if exists is true' do
|
132
|
+
expect(@object).to receive(:exists?).once.and_return(true)
|
133
|
+
|
134
|
+
expect(@mutex.locked?).to be(true)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'returns false if exists is false' do
|
138
|
+
expect(@object).to receive(:exists?).once.and_return(false)
|
139
|
+
|
140
|
+
expect(@mutex.locked?).to be(false)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '.owned?' do
|
145
|
+
it 'returns false if mutex is not locked' do
|
146
|
+
expect(@mutex).to receive(:locked?).once.and_return(false)
|
147
|
+
|
148
|
+
expect(@mutex.owned?).to be(false)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns false if mutex is locked but object size != uuid size' do
|
152
|
+
expect(@mutex).to receive(:locked?).once.and_return(true)
|
153
|
+
expect(@object).to receive(:size).once.and_return(@uuid.size + 10)
|
154
|
+
|
155
|
+
expect(@mutex.owned?).to be(false)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'returns false if mutex is locked and object size == uuid size but object content != uuid' do
|
159
|
+
expect(@mutex).to receive(:locked?).once.and_return(true)
|
160
|
+
expect(@object).to receive(:size).once.and_return(@uuid.size)
|
161
|
+
|
162
|
+
download = StringIO.new('blah')
|
163
|
+
expect(@object).to receive(:download).once.and_return(download)
|
164
|
+
|
165
|
+
expect(@mutex.owned?).to be(false)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'returns true if mutex is locked and object contains uuid' do
|
169
|
+
expect(@mutex).to receive(:locked?).once.and_return(true)
|
170
|
+
expect(@object).to receive(:size).once.and_return(@uuid.size)
|
171
|
+
|
172
|
+
download = StringIO.new(@uuid)
|
173
|
+
expect(@object).to receive(:download).once.and_return(download)
|
174
|
+
|
175
|
+
expect(@mutex.owned?).to be(true)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe '.synchronize' do
|
180
|
+
it 'locks, yields and unlock the mutex' do
|
181
|
+
expect(@mutex).to receive(:owned?).once.and_return(false)
|
182
|
+
expect(@mutex).to receive(:lock).once.and_return(true)
|
183
|
+
expect(@mutex).to receive(:unlock).once
|
184
|
+
|
185
|
+
has_yielded = false
|
186
|
+
@mutex.synchronize do
|
187
|
+
has_yielded = true
|
188
|
+
end
|
189
|
+
|
190
|
+
expect(has_yielded).to be(true)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'raises an error if the lock is already owned' do
|
194
|
+
expect(@mutex).to receive(:owned?).once.and_return(true)
|
195
|
+
expect(@mutex).not_to receive(:lock)
|
196
|
+
expect(@mutex).not_to receive(:unlock)
|
197
|
+
|
198
|
+
has_yielded = false
|
199
|
+
|
200
|
+
expect do
|
201
|
+
@mutex.synchronize do
|
202
|
+
has_yielded = true
|
203
|
+
end
|
204
|
+
end.to raise_error(GCSLock::LockAlreadyOwnedError)
|
205
|
+
|
206
|
+
expect(has_yielded).to be(false)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe '.try_lock' do
|
211
|
+
before do
|
212
|
+
@service = instance_double(Google::Cloud::Storage::Service)
|
213
|
+
allow(@gcs).to receive(:service).and_return(@service)
|
214
|
+
|
215
|
+
@servicev1 = instance_double(Google::Apis::StorageV1::StorageService)
|
216
|
+
allow(@service).to receive(:service).and_return(@servicev1)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns true if lock obtained' do
|
220
|
+
expect(@servicev1).to receive(:insert_object).with(
|
221
|
+
@bucket_name,
|
222
|
+
name: @object_name,
|
223
|
+
if_generation_match: 0,
|
224
|
+
upload_source: instance_of(StringIO),
|
225
|
+
).once
|
226
|
+
|
227
|
+
expect(@mutex.try_lock).to be(true)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'returns false if lock already taken (precondition failed)' do
|
231
|
+
client_error = Google::Apis::ClientError.new('conditionNotMet: Precondition failed', status_code: 412)
|
232
|
+
|
233
|
+
expect(@servicev1).to receive(:insert_object).with(
|
234
|
+
@bucket_name,
|
235
|
+
name: @object_name,
|
236
|
+
if_generation_match: 0,
|
237
|
+
upload_source: instance_of(StringIO),
|
238
|
+
).once.and_raise(client_error)
|
239
|
+
|
240
|
+
expect(@mutex.try_lock).to be(false)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'raises in case of precondition failed for other reason than conditionNotMet' do
|
244
|
+
client_error = Google::Apis::ClientError.new('blah: Precondition failed', status_code: 412)
|
245
|
+
|
246
|
+
expect(@servicev1).to receive(:insert_object).with(
|
247
|
+
@bucket_name,
|
248
|
+
name: @object_name,
|
249
|
+
if_generation_match: 0,
|
250
|
+
upload_source: instance_of(StringIO),
|
251
|
+
).once.and_raise(client_error)
|
252
|
+
|
253
|
+
expect do
|
254
|
+
@mutex.try_lock
|
255
|
+
end.to raise_error(client_error)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'raises in case of other error than precondition failed' do
|
259
|
+
client_error = Google::Apis::ClientError.new('blah', status_code: 400)
|
260
|
+
|
261
|
+
expect(@servicev1).to receive(:insert_object).with(
|
262
|
+
@bucket_name,
|
263
|
+
name: @object_name,
|
264
|
+
if_generation_match: 0,
|
265
|
+
upload_source: instance_of(StringIO),
|
266
|
+
).once.and_raise(client_error)
|
267
|
+
|
268
|
+
expect do
|
269
|
+
@mutex.try_lock
|
270
|
+
end.to raise_error(client_error)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
describe '.unlock' do
|
275
|
+
it 'calls delete on the object if lock is owned' do
|
276
|
+
expect(@mutex).to receive(:owned?).once.and_return(true)
|
277
|
+
expect(@object).to receive(:delete).once
|
278
|
+
|
279
|
+
@mutex.unlock
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'raises an error if the lock is not owned' do
|
283
|
+
expect(@mutex).to receive(:owned?).once.and_return(false)
|
284
|
+
expect(@object).not_to receive(:delete)
|
285
|
+
|
286
|
+
expect do
|
287
|
+
@mutex.unlock
|
288
|
+
end.to raise_error(GCSLock::LockNotOwnedError)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe '.unlock!' do
|
293
|
+
it 'calls delete on the object' do
|
294
|
+
expect(@object).to receive(:delete).once
|
295
|
+
|
296
|
+
@mutex.unlock!
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'raises an error if the object is not found' do
|
300
|
+
expect(@object).to receive(:delete).once.and_raise(Google::Cloud::NotFoundError.new('blah'))
|
301
|
+
|
302
|
+
expect do
|
303
|
+
@mutex.unlock!
|
304
|
+
end.to raise_error(GCSLock::LockNotFoundError)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gcslock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Raphaël Beamonte
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-05-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: google-api-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google-cloud-storage
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.26.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.26.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: codecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Allows to use a Google Cloud Storage bucket as a distributed locking
|
70
|
+
system
|
71
|
+
email:
|
72
|
+
- raphael.beamonte@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- Gemfile
|
79
|
+
- README.md
|
80
|
+
- bin/rspec
|
81
|
+
- gcslock.gemspec
|
82
|
+
- lib/gcslock/errors.rb
|
83
|
+
- lib/gcslock/mutex.rb
|
84
|
+
- lib/gcslock/version.rb
|
85
|
+
- spec/gcslock/mutex_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
homepage: https://github.com/XaF/gcslock-ruby
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata:
|
91
|
+
allowed_push_host: https://rubygems.org
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '2.0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.0.3
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Google Cloud Storage distributed locking
|
111
|
+
test_files: []
|