bucket_brigade 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -5,7 +5,8 @@ like Riak CS.
5
5
  Usage:
6
6
 
7
7
  ```ruby
8
- require 'bucket_brigade/bucket_config
8
+ require 'bucket_brigade/bucket_config'
9
+ require 'bucket_brigade/bucket_list'
9
10
 
10
11
  bucket_config1 = BucketBrigade::BucketConfig.new(
11
12
  'http://192.168.37.73:8080',
@@ -21,15 +22,14 @@ bucket_config2 = BucketBrigade::BucketConfig.new(
21
22
  'test-bucket'
22
23
  )
23
24
 
24
-
25
- bucket_list = BucketBrigade::BucketList.new(
25
+ bucket_list = BucketBrigade::BucketList.build(
26
26
  bucket_config1,
27
27
  bucket_config2,
28
- )
28
+ )
29
29
 
30
30
  open('/tmp/file') do |f|
31
31
  bucket_list.put('test-key', f)
32
32
  end
33
33
 
34
- puts bucket_list.get('test-key')
34
+ puts bucket_list.get('test-key').read
35
35
  ```
@@ -1,8 +1,11 @@
1
1
  # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bucket_brigade/version'
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = 'bucket_brigade'
5
- s.version = '0.0.1'
8
+ s.version = BucketBrigade::VERSION
6
9
  s.summary = 'S3 bucket cache hierarchy'
7
10
  s.description = 'S3 bucket cache hierarchy'
8
11
  s.author = 'Pivotal Labs'
@@ -1,19 +1,17 @@
1
1
  require 'bucket_brigade'
2
-
3
2
  require 'aws-sdk'
4
-
5
3
  require 'tempfile'
6
4
 
7
5
  class BucketBrigade::Bucket
8
6
  def initialize(bucket_config)
9
- @s3_bucket = AWS::S3.new(
10
- s3_endpoint: bucket_config.host,
11
- s3_port: bucket_config.port,
12
- access_key_id: bucket_config.access_key_id,
13
- secret_access_key: bucket_config.secret_access_key,
14
- use_ssl: bucket_config.use_ssl,
15
- s3_force_path_style: true
16
- ).buckets[bucket_config.bucket]
7
+ @bucket_config = bucket_config
8
+ end
9
+
10
+ def can_connect?
11
+ s3.buckets.first
12
+ true
13
+ rescue Timeout::Error
14
+ false
17
15
  end
18
16
 
19
17
  def has_key?(key)
@@ -33,7 +31,7 @@ class BucketBrigade::Bucket
33
31
  end
34
32
 
35
33
  def object(key)
36
- @s3_bucket.objects[key]
34
+ s3_bucket.objects[key]
37
35
  end
38
36
 
39
37
  def copy(key, other)
@@ -48,4 +46,23 @@ class BucketBrigade::Bucket
48
46
 
49
47
  temp.close!
50
48
  end
49
+
50
+ private
51
+
52
+ attr_reader :bucket_config
53
+
54
+ def s3
55
+ @s3 ||= AWS::S3.new(
56
+ s3_endpoint: bucket_config.host,
57
+ s3_port: bucket_config.port,
58
+ access_key_id: bucket_config.access_key_id,
59
+ secret_access_key: bucket_config.secret_access_key,
60
+ use_ssl: bucket_config.use_ssl,
61
+ s3_force_path_style: true
62
+ )
63
+ end
64
+
65
+ def s3_bucket
66
+ @s3_bucket ||= s3.buckets[bucket_config.bucket]
67
+ end
51
68
  end
@@ -4,17 +4,32 @@ require 'bucket_brigade/bucket'
4
4
  require 'aws-sdk'
5
5
 
6
6
  class BucketBrigade::BucketList
7
- def initialize(*bucket_configs)
8
- @buckets = bucket_configs.map do |bucket_config|
9
- BucketBrigade::Bucket.new(bucket_config)
7
+ def self.build(bucket_configs)
8
+ good_buckets = []
9
+
10
+ bucket_configs.each do |bucket_config|
11
+ bucket = BucketBrigade::Bucket.new(bucket_config)
12
+ if bucket.can_connect?
13
+ good_buckets << bucket
14
+ else
15
+ warn "Unable to connect to bucket at #{bucket_config.endpoint}"
16
+ end
10
17
  end
18
+
19
+ new(good_buckets)
20
+ end
21
+
22
+ attr_reader :buckets
23
+
24
+ def initialize(buckets)
25
+ @buckets = buckets
11
26
  end
12
27
 
13
28
  def get(key)
14
- @buckets.each_with_index do |bucket, i|
29
+ buckets.each_with_index do |bucket, i|
15
30
  if bucket.has_key?(key)
16
31
  if i > 0
17
- prev_bucket = @buckets[i - 1]
32
+ prev_bucket = buckets[i - 1]
18
33
  bucket.copy(key, prev_bucket)
19
34
  return prev_bucket.get(key)
20
35
  end
@@ -25,9 +40,9 @@ class BucketBrigade::BucketList
25
40
  end
26
41
 
27
42
  def put(key, io)
28
- @buckets.last.put(key, io)
43
+ buckets.last.put(key, io)
29
44
  # invalidate all the way down
30
- @buckets.first(@buckets.size - 1).each do |bucket|
45
+ buckets.first(buckets.size - 1).each do |bucket|
31
46
  bucket.delete(key)
32
47
  end
33
48
  end
@@ -0,0 +1,3 @@
1
+ module BucketBrigade
2
+ VERSION = '0.1.0'
3
+ end
@@ -25,11 +25,11 @@ describe 'put and get data from the cache' do
25
25
  'bucket-brigade-test'
26
26
  )
27
27
 
28
- bucket_list = BucketBrigade::BucketList.new(
28
+ bucket_list = BucketBrigade::BucketList.build([
29
29
  bucket_config1,
30
30
  bucket_config2,
31
31
  bucket_config3
32
- )
32
+ ])
33
33
 
34
34
  io = StringIO.new
35
35
  io.write('test 1 2 3')
@@ -2,148 +2,181 @@ require 'bucket_brigade/bucket_config'
2
2
  require 'bucket_brigade/bucket_list'
3
3
 
4
4
  describe BucketBrigade::BucketList do
5
+ describe '.from_configs' do
6
+ describe 'when one of the buckets cannot connect' do
7
+ let(:working_bucket) { double('working bucket', can_connect?: true) }
8
+ let(:bad_bucket) { double('bad bucket', can_connect?: false) }
9
+ let(:working_config) { double('working config') }
10
+ let(:bad_config) { double('bad config', endpoint: 'bad.example.com') }
11
+ let(:s3_object) { double('s3 object') }
5
12
 
6
- let(:bucket_configs) {
7
- [
13
+ before do
14
+ expect(BucketBrigade::Bucket).to receive(:new).with(working_config).and_return(working_bucket)
15
+ expect(BucketBrigade::Bucket).to receive(:new).with(bad_config).and_return(bad_bucket)
16
+ end
17
+
18
+ subject(:bucket_list) { described_class.build([bad_config, working_config]) }
19
+
20
+ it 'creates a bucket list with only the working bucket' do
21
+ expect(bucket_list.buckets).to eq([working_bucket])
22
+ end
23
+
24
+ it 'only uses the working bucket' do
25
+ expect(working_bucket).to receive(:has_key?).with('key').and_return(true)
26
+ expect(working_bucket).to receive(:get).with('key').and_return(s3_object)
27
+ expect(bad_bucket).not_to receive(:get)
28
+
29
+ expect(bucket_list.get('key')).to eq(s3_object)
30
+ end
31
+ end
32
+ end
33
+
34
+ describe 'instance methods' do
35
+ let(:bucket_1) do
36
+ BucketBrigade::Bucket.new(
8
37
  BucketBrigade::BucketConfig.new(
9
- 'http://test-endpoint-1:1234',
10
- 'test-access-key-id-1',
11
- 'test-secret-access-key-1',
12
- 'test-bucket-1'
13
- ),
38
+ 'http://test-endpoint-1:1234',
39
+ 'test-access-key-id-1',
40
+ 'test-secret-access-key-1',
41
+ 'test-bucket-1'
42
+ )
43
+ )
44
+ end
45
+
46
+ let(:bucket_2) do
47
+ BucketBrigade::Bucket.new(
14
48
  BucketBrigade::BucketConfig.new(
15
- 'http://test-endpoint-2:1234',
16
- 'test-access-key-id-2',
17
- 'test-secret-access-key-2',
18
- 'test-bucket-2'
49
+ 'http://test-endpoint-2:1234',
50
+ 'test-access-key-id-2',
51
+ 'test-secret-access-key-2',
52
+ 'test-bucket-2'
19
53
  )
20
- ]
21
- }
54
+ )
55
+ end
22
56
 
23
- let(:s3_1) { instance_double(AWS::S3) }
24
- let(:s3_2) { instance_double(AWS::S3) }
57
+ let(:buckets) { [bucket_1, bucket_2] }
25
58
 
26
- let(:bucket_collection_1) { instance_double(AWS::S3::BucketCollection) }
27
- let(:bucket_collection_2) { instance_double(AWS::S3::BucketCollection) }
59
+ let(:s3_1) { instance_double(AWS::S3, buckets: bucket_collection_1) }
60
+ let(:s3_2) { instance_double(AWS::S3, buckets: bucket_collection_2) }
28
61
 
29
- let(:bucket_1) { instance_double(AWS::S3::Bucket) }
30
- let(:bucket_2) { instance_double(AWS::S3::Bucket) }
62
+ let(:bucket_collection_1) { instance_double(AWS::S3::BucketCollection, first: nil) }
63
+ let(:bucket_collection_2) { instance_double(AWS::S3::BucketCollection, first: nil) }
31
64
 
32
- let(:object_collection_1) { instance_double(AWS::S3::ObjectCollection) }
33
- let(:object_collection_2) { instance_double(AWS::S3::ObjectCollection) }
65
+ let(:s3_bucket_1) { instance_double(AWS::S3::Bucket, objects: object_collection_1) }
66
+ let(:s3_bucket_2) { instance_double(AWS::S3::Bucket, objects: object_collection_2) }
34
67
 
35
- let(:object_1) { instance_double(AWS::S3::S3Object) }
36
- let(:object_2) { instance_double(AWS::S3::S3Object) }
68
+ let(:object_collection_1) { instance_double(AWS::S3::ObjectCollection) }
69
+ let(:object_collection_2) { instance_double(AWS::S3::ObjectCollection) }
37
70
 
38
- before do
39
- allow(AWS::S3).to receive(:new).with(
40
- s3_endpoint: 'test-endpoint-1',
41
- s3_port: 1234,
42
- access_key_id: 'test-access-key-id-1',
43
- secret_access_key: 'test-secret-access-key-1',
44
- use_ssl: false,
71
+ let(:object_1) { instance_double(AWS::S3::S3Object) }
72
+ let(:object_2) { instance_double(AWS::S3::S3Object) }
73
+
74
+ before do
75
+ allow(AWS::S3).to receive(:new).with(
76
+ s3_endpoint: 'test-endpoint-1',
77
+ s3_port: 1234,
78
+ access_key_id: 'test-access-key-id-1',
79
+ secret_access_key: 'test-secret-access-key-1',
80
+ use_ssl: false,
45
81
  s3_force_path_style: true
46
- ).and_return(s3_1)
47
- allow(s3_1).to receive(:buckets).and_return(bucket_collection_1)
48
- allow(bucket_collection_1).to receive(:[]).with('test-bucket-1').and_return(bucket_1)
49
- allow(bucket_1).to receive(:objects).and_return(object_collection_1)
50
- allow(object_collection_1).to receive(:[]).with('test-key').and_return(object_1)
51
-
52
- allow(AWS::S3).to receive(:new).with(
53
- s3_endpoint: 'test-endpoint-2',
54
- s3_port: 1234,
55
- access_key_id: 'test-access-key-id-2',
56
- secret_access_key: 'test-secret-access-key-2',
57
- use_ssl: false,
82
+ ).and_return(s3_1)
83
+ allow(bucket_collection_1).to receive(:[]).with('test-bucket-1').and_return(s3_bucket_1)
84
+ allow(object_collection_1).to receive(:[]).with('test-key').and_return(object_1)
85
+
86
+ allow(AWS::S3).to receive(:new).with(
87
+ s3_endpoint: 'test-endpoint-2',
88
+ s3_port: 1234,
89
+ access_key_id: 'test-access-key-id-2',
90
+ secret_access_key: 'test-secret-access-key-2',
91
+ use_ssl: false,
58
92
  s3_force_path_style: true
59
- ).and_return(s3_2)
60
- allow(s3_2).to receive(:buckets).and_return(bucket_collection_2)
61
- allow(bucket_collection_2).to receive(:[]).with('test-bucket-2').and_return(bucket_2)
62
- allow(bucket_2).to receive(:objects).and_return(object_collection_2)
63
- allow(object_collection_2).to receive(:[]).with('test-key').and_return(object_2)
64
- end
93
+ ).and_return(s3_2)
94
+ allow(bucket_collection_2).to receive(:[]).with('test-bucket-2').and_return(s3_bucket_2)
95
+ allow(object_collection_2).to receive(:[]).with('test-key').and_return(object_2)
96
+ end
65
97
 
66
- subject(:bucket_list) { described_class.new(*bucket_configs) }
98
+ subject(:bucket_list) { described_class.new(buckets) }
67
99
 
68
- describe '#get' do
69
- context 'when the object is found in the first bucket' do
70
- before do
71
- allow(object_1).to receive(:exists?).and_return(true)
72
- end
100
+ describe '#get' do
101
+ context 'when the object is found in the first bucket' do
102
+ before do
103
+ allow(object_1).to receive(:exists?).and_return(true)
104
+ end
73
105
 
74
- it 'returns the object from the first bucket' do
75
- expect(bucket_list.get('test-key')).to eq(object_1)
106
+ it 'returns the object from the first bucket' do
107
+ expect(bucket_list.get('test-key')).to eq(object_1)
108
+ end
76
109
  end
77
- end
78
110
 
79
- context 'when the object is not found in any buckets' do
80
- before do
81
- allow(object_1).to receive(:exists?).and_return(false)
82
- allow(object_2).to receive(:exists?).and_return(false)
83
- end
111
+ context 'when the object is not found in any buckets' do
112
+ before do
113
+ allow(object_1).to receive(:exists?).and_return(false)
114
+ allow(object_2).to receive(:exists?).and_return(false)
115
+ end
84
116
 
85
- it 'returns nil' do
86
- expect(bucket_list.get('test-key')).to eq(nil)
117
+ it 'returns nil' do
118
+ expect(bucket_list.get('test-key')).to eq(nil)
119
+ end
87
120
  end
88
- end
89
-
90
- context 'when the object is found in the second bucket' do
91
- let(:temp) { instance_double(Tempfile) }
92
121
 
93
- before do
94
- allow(object_1).to receive(:exists?).and_return(false)
95
- allow(object_2).to receive(:exists?).and_return(true)
122
+ context 'when the object is found in the second bucket' do
123
+ let(:temp) { instance_double(Tempfile) }
96
124
 
97
- allow(Tempfile).to receive(:new).with('bucket-copy').and_return(temp)
98
- allow(temp).to receive(:rewind)
99
- allow(temp).to receive(:close!)
100
- end
125
+ before do
126
+ allow(object_1).to receive(:exists?).and_return(false)
127
+ allow(object_2).to receive(:exists?).and_return(true)
101
128
 
102
- it 'writes the object to the first bucket' do
103
- allow(object_2).to receive(:read)
104
- expect(object_1).to receive(:write).with(temp)
105
- bucket_list.get('test-key')
106
- end
129
+ allow(Tempfile).to receive(:new).with('bucket-copy').and_return(temp)
130
+ allow(temp).to receive(:rewind)
131
+ allow(temp).to receive(:close!)
132
+ end
107
133
 
108
- it 'returns the object from the first bucket' do
109
- allow(object_2).to receive(:read)
134
+ it 'writes the object to the first bucket' do
135
+ allow(object_2).to receive(:read)
136
+ expect(object_1).to receive(:write).with(temp)
137
+ bucket_list.get('test-key')
138
+ end
110
139
 
111
- allow(object_1).to receive(:write).with(temp) {
112
- allow(object_1).to receive(:exists?).and_return(true)
113
- }
114
- expect(bucket_list.get('test-key')).to eq(object_1)
115
- end
140
+ it 'returns the object from the first bucket' do
141
+ allow(object_2).to receive(:read)
116
142
 
117
- end
143
+ allow(object_1).to receive(:write).with(temp) {
144
+ allow(object_1).to receive(:exists?).and_return(true)
145
+ }
146
+ expect(bucket_list.get('test-key')).to eq(object_1)
147
+ end
118
148
 
119
- context 'when the object is found in both buckets' do
120
- before do
121
- allow(object_1).to receive(:exists?).and_return(true)
122
- allow(object_2).to receive(:exists?).and_return(true)
123
149
  end
124
150
 
125
- it 'returns the object from the first bucket' do
126
- expect(bucket_list.get('test-key')).to eq(object_1)
151
+ context 'when the object is found in both buckets' do
152
+ before do
153
+ allow(object_1).to receive(:exists?).and_return(true)
154
+ allow(object_2).to receive(:exists?).and_return(true)
155
+ end
156
+
157
+ it 'returns the object from the first bucket' do
158
+ expect(bucket_list.get('test-key')).to eq(object_1)
159
+ end
127
160
  end
128
161
  end
129
- end
130
162
 
131
- describe '#put' do
132
- context 'when writing an object' do
133
- let(:io) { instance_double(File) }
163
+ describe '#put' do
164
+ context 'when writing an object' do
165
+ let(:io) { instance_double(File) }
134
166
 
135
- it 'puts the object in the last bucket' do
136
- allow(object_1).to receive(:delete)
167
+ it 'puts the object in the last bucket' do
168
+ allow(object_1).to receive(:delete)
137
169
 
138
- expect(object_2).to receive(:write).with(io)
139
- bucket_list.put('test-key', io)
140
- end
170
+ expect(object_2).to receive(:write).with(io)
171
+ bucket_list.put('test-key', io)
172
+ end
141
173
 
142
- it 'delete the object from the first bucket' do
143
- allow(object_2).to receive(:write).with(io)
174
+ it 'delete the object from the first bucket' do
175
+ allow(object_2).to receive(:write).with(io)
144
176
 
145
- expect(object_1).to receive(:delete)
146
- bucket_list.put('test-key', io)
177
+ expect(object_1).to receive(:delete)
178
+ bucket_list.put('test-key', io)
179
+ end
147
180
  end
148
181
  end
149
182
  end
@@ -1,10 +1,8 @@
1
1
  require 'bucket_brigade/bucket'
2
2
  require 'bucket_brigade/bucket_config'
3
-
4
3
  require 'aws-sdk'
5
4
 
6
5
  describe BucketBrigade::Bucket do
7
-
8
6
  subject(:bucket) { described_class.new(bucket_config) }
9
7
 
10
8
  let(:bucket_config) {
@@ -36,6 +34,37 @@ describe BucketBrigade::Bucket do
36
34
  allow(object_collection).to receive(:[]).with('test-key').and_return(object)
37
35
  end
38
36
 
37
+ describe '#can_connect?' do
38
+ context 'when able to connect to the S3 bucket' do
39
+ it 'returns true' do
40
+ allow(bucket_collection).to receive(:first)
41
+ expect(bucket.can_connect?).to eq(true)
42
+ end
43
+ end
44
+
45
+ context 'when unable to connect to the S3 bucket' do
46
+ before do
47
+ bad_bucket_collection = double
48
+ expect(bad_bucket_collection).to receive(:first).and_raise(error_class)
49
+ expect(s3).to receive(:buckets).and_return(bad_bucket_collection)
50
+ end
51
+
52
+ context 'because of a read timeout' do
53
+ let(:error_class) { Net::ReadTimeout }
54
+ it 'returns false' do
55
+ expect(bucket.can_connect?).to eq(false)
56
+ end
57
+ end
58
+
59
+ context 'because of an open timeout' do
60
+ let(:error_class) { Net::OpenTimeout }
61
+ it 'returns false' do
62
+ expect(bucket.can_connect?).to eq(false)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
39
68
  describe '#has_key' do
40
69
  context 'when the object does not exist' do
41
70
  before { allow(object).to receive(:exists?).and_return(false) }
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bucket_brigade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Pivotal Labs
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-08-01 00:00:00.000000000 Z
12
+ date: 2014-08-14 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
@@ -20,6 +22,7 @@ dependencies:
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
@@ -27,6 +30,7 @@ dependencies:
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: rspec-legacy_formatters
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ~>
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ~>
39
44
  - !ruby/object:Gem::Version
@@ -41,6 +46,7 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: aws-sdk
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
51
  - - ~>
46
52
  - !ruby/object:Gem::Version
@@ -48,6 +54,7 @@ dependencies:
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - ~>
53
60
  - !ruby/object:Gem::Version
@@ -67,6 +74,7 @@ files:
67
74
  - lib/bucket_brigade/bucket.rb
68
75
  - lib/bucket_brigade/bucket_config.rb
69
76
  - lib/bucket_brigade/bucket_list.rb
77
+ - lib/bucket_brigade/version.rb
70
78
  - spec/integration/put_get_spec.rb
71
79
  - spec/unit/bucket_brigade/bucket_config_spec.rb
72
80
  - spec/unit/bucket_brigade/bucket_list_spec.rb
@@ -74,25 +82,26 @@ files:
74
82
  homepage: https://github.com/pivotal-cf-experimental/bucket_brigade
75
83
  licenses:
76
84
  - Apache 2.0
77
- metadata: {}
78
85
  post_install_message:
79
86
  rdoc_options: []
80
87
  require_paths:
81
88
  - lib
82
89
  required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
83
91
  requirements:
84
- - - '>='
92
+ - - ! '>='
85
93
  - !ruby/object:Gem::Version
86
94
  version: '0'
87
95
  required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
88
97
  requirements:
89
- - - '>='
98
+ - - ! '>='
90
99
  - !ruby/object:Gem::Version
91
100
  version: '0'
92
101
  requirements: []
93
102
  rubyforge_project:
94
- rubygems_version: 2.0.14
103
+ rubygems_version: 1.8.23
95
104
  signing_key:
96
- specification_version: 4
105
+ specification_version: 3
97
106
  summary: S3 bucket cache hierarchy
98
107
  test_files: []
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 8cdb69753762ea4144d0ac29a7debeff8fcefe8d
4
- data.tar.gz: 9dfc9ad55eba0aa9e4b923949e477023a13b7c49
5
- SHA512:
6
- metadata.gz: 31a691b784f454d86ee13e308622cb51ebaff8bc3a9a3c714fa94745025e4ba71738ce82513a36f9ba6a9e2b9a7bd5252408ff1e9a09e77a02b0dfd71bc56009
7
- data.tar.gz: a1f16f71752d1488930d0308fcb3865791a87f3dd59799051431ba5171795f2ed2902bca6b26ce6530e80461374f6d411442a06cc527bfddbe7884f23a1b51f6