s3_data_packer 0.2.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da63067cb5437094daab442088507425866787ad176a98a0f7e515ae54f142fe
4
+ data.tar.gz: 249ffbb44f8e245c8383e17c1c6ae754e477935e8b461ed9193b8a12beb4394f
5
+ SHA512:
6
+ metadata.gz: 7f204a257f1b92d40e2e425b62e66e88238f06205e2da40940243cd086ce66005eb00ed25d3326bb25b6ee65351ca46a85165f7117d8526ec9f9de22a2cff165
7
+ data.tar.gz: 9c6681710e72702cb85fa63bf91a891afc32771626449f5dfc314ebd2f69fd8a52a279d0d1d889a15b4a016e9695ad2846f3eb395244df17f45ed7d5b5e4e0bf
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ log/*.log*
10
+ *~
11
+ *#*#*
12
+ .DS_Store
13
+ coverage/*
14
+ *.swp
15
+ *.iml
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ s3_data_packer
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.9
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.9
7
+ before_install: gem install bundler -v 1.16.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # v0.2.1
2
+
3
+ - Adding custom sources/targets wrappers
4
+ - More testing
5
+
6
+ # Unreleased
7
+
8
+ - First commit
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at rayko.drg@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in s3_data_packer.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,54 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ s3_data_packer (0.2.0)
5
+ aws-sdk-s3 (~> 1)
6
+ mime-types (~> 3)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ aws-eventstream (1.1.1)
12
+ aws-partitions (1.492.0)
13
+ aws-sdk-core (3.119.1)
14
+ aws-eventstream (~> 1, >= 1.0.2)
15
+ aws-partitions (~> 1, >= 1.239.0)
16
+ aws-sigv4 (~> 1.1)
17
+ jmespath (~> 1.0)
18
+ aws-sdk-kms (1.47.0)
19
+ aws-sdk-core (~> 3, >= 3.119.0)
20
+ aws-sigv4 (~> 1.1)
21
+ aws-sdk-s3 (1.100.0)
22
+ aws-sdk-core (~> 3, >= 3.119.0)
23
+ aws-sdk-kms (~> 1)
24
+ aws-sigv4 (~> 1.1)
25
+ aws-sigv4 (1.2.4)
26
+ aws-eventstream (~> 1, >= 1.0.2)
27
+ byebug (11.1.3)
28
+ docile (1.4.0)
29
+ jmespath (1.4.0)
30
+ mime-types (3.3.1)
31
+ mime-types-data (~> 3.2015)
32
+ mime-types-data (3.2021.0704)
33
+ minitest (5.14.4)
34
+ rake (10.5.0)
35
+ simplecov (0.21.2)
36
+ docile (~> 1.1)
37
+ simplecov-html (~> 0.11)
38
+ simplecov_json_formatter (~> 0.1)
39
+ simplecov-html (0.12.3)
40
+ simplecov_json_formatter (0.1.3)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ bundler (~> 1.16)
47
+ byebug
48
+ minitest (~> 5.0)
49
+ rake (~> 10.0)
50
+ s3_data_packer!
51
+ simplecov
52
+
53
+ BUNDLED WITH
54
+ 1.16.6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Rayko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # S3DataPacker
2
+
3
+ This small packer will read a large amount of individual files on an S3 location that represent single
4
+ items in JSON format, and pack them into larget batches with the option of compressing the final batch,
5
+ decreasing the total storage size of the data (if compressed), and also reducing the total number of files.
6
+
7
+ The idea is to prepare data dumped on S3 in this way to a more optimal layout for AWS Athena to setup
8
+ a querying system on top of it.
9
+
10
+ For now, S3DataPacker supports JSON items, with a 1 item per file layout, GZip compression if enabled, and
11
+ only from S3 to S3, though the source and target bucket can be different buckets or even on different accounts
12
+ if the proper credentials are provided.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 's3_data_packer'
20
+ ```
21
+
22
+ Or use the `main` branch from repo:
23
+
24
+ ```ruby
25
+ gem 's3_data_packer', git: 'https://github.com/rayko/s3_data_packer.git', branch: 'main'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install s3_data_packer
35
+
36
+ ## Configurations
37
+
38
+ There's a good number of options that can alter how the data is consumed. Below is the list of all defaults
39
+ out of the box:
40
+
41
+ ```
42
+ S3DataPacker.configure do |config|
43
+ config.logger = Logger.new('log/s3_data_packer.log') # Standard logger for information
44
+ config.thread_count = 2 # How many threads to run
45
+ config.thread_sleep_time = 1 # How long to wait when there's no work in queue
46
+ config.thread_lock_wait_time = 1 # How long to wait when a lock error happens before retrying
47
+ config.max_queue_size = 10000 # How big can the queue get during processing
48
+ config.max_queue_wait = 5 # How long to wait when the queue reached max_queue_size before continuing
49
+ config.workdir = 'tmp/s3_data_packer' # Where to keep output files until pushing to target location
50
+ config.cleanup_batch = true # Whether to remove the pushed batches or not
51
+ config.compress_batch = true # Whether to compresss with GZip or not
52
+ config.batch_size = 100000 # How many items to fit in a batch
53
+ config.s3_api_key = nil # Default API Key for an Aws account
54
+ config.s3_api_secret = nil # Default API Secret for an AWS account
55
+ config.s3_region = nil # Default region for the buckets to use
56
+ config.output_filename_prefix = nil # Static prefix to append on output filenames
57
+ config.output_filename_suffix = 'batch' # Static suffix to insert on output filenames
58
+ config.output_filename_pattern = %i[timecode_int suffix] # Simple pattern to construct output filenames (more on that below)
59
+ config.output_filename_splitter = '_' # Character to join elements into a string that'll be a final filename
60
+ end
61
+ ```
62
+
63
+ ### S3 Credentials
64
+
65
+ There are 2 main ways to do this depending on the context. Buckets can be configured in place with user provided
66
+ credentials for both source and target locations.
67
+
68
+ If the source and target locations are on the same account, region and use the same credentials, the options
69
+ above can be set to always set those credentials.
70
+
71
+ AWS credentials in the configuration here are optional, and just a shortcut to setting credentials for each
72
+ run.
73
+
74
+ ### Thread options
75
+
76
+ Various thread options are available to moderate how the process run. Depending on the hardware available
77
+ the thread counts can be adjusted to speed up the process. However, it there are enough threads, the queue
78
+ might run empty too soon, in which case threads will sleep the given ammount of time to wait to gather
79
+ some items to work on.
80
+
81
+ All timming settings should be adjusted depending on where this is going to run and the resources available.
82
+
83
+ ### Output filename options
84
+
85
+ There are a couple parameters that can be configured generally to generate filenames consistently. The simplest
86
+ options `:output_filename_prefix`, `:output_filename_suffix` and `:output_filename_splitter` are straight
87
+ forward. The `:output_filename_pattern` option is a bit more involved. It basically dictates order and what
88
+ values to use when generating a filename. When a new name needs to be generated, each item in the pattern will
89
+ be translated to a value of some kind, and merged toghether with the `:output_filename_splitter` character.
90
+ The contents of the pattern array must be `Symbol` names and can only be one of the following:
91
+
92
+ - :timecode_int -> current standard time in seconds (`Time.now.to_i`)
93
+ - :timecode_dec -> current standard time with milliseconds (`Time.now.to_f`)
94
+ - :number -> a simple number that grows as new names are generated
95
+ - :timestamp -> simple time stamp with format YYYYMMDDhhmmss
96
+ - :datestamp -> simple date stamp with format YYYYMMDD
97
+ - :prefix -> given static string to use as prefix on the name
98
+ - :suffix -> given static string to use as suffix on the name
99
+
100
+ Different patterns will generate different names with same structuring. The important part here is to always
101
+ include a variable element so final files do not override previous data.
102
+
103
+ A few examples of different patterns, setting prefix as 'data' and suffix as 'batch':
104
+
105
+ - [:timecode_int, :suffix] -> 1111111111_batch 1111111112_batch 1111111113_batch ...
106
+ - [:datestamp, :number] -> 20200101_1 20200101_2 20200101_3 ...
107
+ - [:prefix, :number, :suffix] -> data_1_batch data_2_batch data_3_batch ...
108
+
109
+ ## Usage
110
+
111
+ The simplest setup for this simple file processor is to set the AWS credentials and region through the
112
+ configuration as shown above. Be sure that `config.workdir` is set and the location exists in the local
113
+ machine.
114
+
115
+ To launch the packer, the only thing needed out of the box, is to instantiate 2 `S3DataPacker::Bucket`
116
+ objects that will act as source and destination:
117
+
118
+ ```
119
+ source_bucket = S3DataPacker::Sources::S3Bucket.new name: 'my-bucket', path: 'some/location'
120
+ target_bucket = S3DataPacker::Sources::S3Bucket.new name: 'other-bucket', path: 'my/destination'
121
+ ```
122
+
123
+ You can override the configured AWS credentials with the `:credentials` option, as well as `:region`.
124
+ `:credentials` needs to be an instance of `Aws::Credentials`. Having it setup this way should allow
125
+ for more complex role invoking, since the instance passed `:credentials` option is fed direclty to
126
+ `Aws::S3::Resource` and `Aws::S3::Client` to interface with the S3 buckets.
127
+
128
+ Once the buckets are instantiated you can call the packer:
129
+
130
+ ```
131
+ packer = S3DataPacker::Packer.new source: source_bucket, target: target_bucket
132
+ packer.pack!
133
+ ```
134
+
135
+ ### How it works?
136
+
137
+ Based on the sample above, what will happen once that `#pack!` is called, is that a set of threads will boot
138
+ up, a new file will be opened in `config.workdir` that, without further configuration it will be named
139
+ `123123123_batch.json` (in general), and then the packer will start to iterate over all keys under the
140
+ source path `some/location`.
141
+
142
+ Each key listed will enter the queue for the threads, and the threads will then take each key in queue,
143
+ download the data in memory (it does not create a file for it), append the data into the currently opened
144
+ batch, and continue with the next key.
145
+
146
+ As items are appended, if the target size `config.batch_size` is reached, the current batch is closed,
147
+ compressed with GZip, and uploaded to target bucket in the location specificed `my/destination`. Once
148
+ the file is pushed, the local copy is deleted, and a new batch is opened to continue appending items.
149
+
150
+ When all the keys have been listed, the packer will wait for the threads to finish any remaining items in the
151
+ queue, and the last opened batch that likely hasn't reached target size, is then closed and pushed like the
152
+ others.
153
+
154
+ And that's basically it. There are a few places in where additional processing may be introduced, but that's
155
+ a feature for later.
156
+
157
+ There are no specialties regarding source and target buckets, they can be the same, on different accounts
158
+ or region. However it is not recommended to setup source and target on the same bucket and path.
159
+
160
+ ### Custom Sources/Targets
161
+
162
+ It is possible to define a custom source and target for the packer to read data from some different place
163
+ that is not an S3 bucket, as well as put the resultant batch into somewhere else. The `S3DataPacker::Packer`
164
+ can take `:source` and `:target` parameters to use other things. At the moment, there are 2 source classes
165
+ provided:
166
+
167
+ - `S3DataPacker::Sources::S3Bucket`
168
+ - `S3DataPacker::Sources::Object`
169
+
170
+ And 2 pre-defined target:
171
+
172
+ - `S3DataPacker::Targets::S3Bucket`
173
+ - `S3DataPacker::Targets::Object`
174
+
175
+ Both bucket related classes operate in the same way, you need to define the name and path of the buckets
176
+ to read and write the data, as in the main example above. Be sure to configure credentials to use these.
177
+
178
+ The object source is pretty much a wrapper you can use with some other custom object, passing down which
179
+ methods to call on it for the packer. Any object you pass down in the object source needs to respond to:
180
+
181
+ - `#name`: which is mostly used for logging
182
+ - `#each`: with a block to iterate over items
183
+ - `#fetch`: with an identifier to find the actual data of the item
184
+
185
+ The `#each` and `#fetch` methods are like that mainly because the packer is threaded and it expects to
186
+ iterate over keys, or IDs or some minor piece of information in one thread, and use that information
187
+ to retrive the full object data on other threads. This keeps the queue small in byte size.
188
+
189
+ By default the object source expects those method names to be defined in the object provided. If there
190
+ are other methods that do that already on the object but with different name, the method names can be
191
+ passed like so:
192
+
193
+ ```ruby
194
+ S3DataPacker::Sources::Object.new object: my_object,
195
+ each_method: :iterate,
196
+ fetch_method: :find,
197
+ name_method: :display_name
198
+ ```
199
+
200
+ As long as `#each` yields items (strings, IDs, whatever), and `#fetch` returns JSON data for an item,
201
+ this should work.
202
+
203
+ For targets, there's also a `S3DataPacker::Targets::Object` that can be used in the a similar way, the only
204
+ 2 methods for it are:
205
+
206
+ - `#name`: for the same purposes as sources `#name` method
207
+ - `#save_file`: with a path parameter
208
+
209
+ It can also be configured with other method names if needed:
210
+
211
+ ```ruby
212
+ S3DataPacker::Targets::Object.new object: my_object,
213
+ name_method: :custom_name,
214
+ save_file_method: :save!
215
+ ```
216
+
217
+ It is also possible to construct a custom source/target class outside of the pre-defined ones that can
218
+ do anything needed, and passed down to the packer instance to use. As long as the few needed methods are
219
+ there, it should work just fine.
220
+
221
+ In some cases it might be useful to unify the get/fetch mechanics. This can be easily done by just
222
+ bypassing the `#fetch` method and returning the data received. If for some reason the iterator for
223
+ `#each` needs to output the actual data right there, by writing a `#fetch` method that returns whatever
224
+ was passed in the parameter, effectively makes the packer's queue hold actual data. This might be useful
225
+ in some cases, though it might need a smaller max size configuration to prevent having too much data in the
226
+ queue.
227
+
228
+ I believe that with these tools, the packer can pretty much do the JSON packing in most cases, including:
229
+
230
+ - Reading database records and serializing them into JSON
231
+ - Reading S3 buckets (as originally intended)
232
+ - Reading NoSQL items
233
+ - Reading one file or a set of files
234
+ - Writing batches into S3 buckets (as originally intended)
235
+ - Writing batches into filesystem on some custom location
236
+ - Writing batches into some other custom location
237
+
238
+ At least, it does cover the cases in where I intend to use it.
239
+
240
+ ## Development
241
+
242
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
243
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
244
+
245
+ ## Contributing
246
+
247
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rayko/s3_data_packer.
248
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to
249
+ adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
250
+
251
+ ## License
252
+
253
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
254
+
255
+ ## Code of Conduct
256
+
257
+ Everyone interacting in the S3DataPacker project’s codebases, issue trackers, chat rooms and mailing lists
258
+ is expected to follow the [code of conduct](https://github.com/[USERNAME]/s3_data_packer/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "s3_data_packer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,88 @@
1
+ module S3DataPacker
2
+ class Bucket
3
+ attr_reader :bucket_name, :path
4
+
5
+ def initialize opts = {}
6
+ @bucket_name = opts[:bucket_name]
7
+ @credentials = opts[:credentials]
8
+ @region = opts[:region]
9
+ @path = opts[:path]
10
+ end
11
+
12
+ def credentials
13
+ @credentials ||= S3DataPacker.config.default_s3_credentials
14
+ end
15
+
16
+ def region
17
+ @region ||= S3DataPacker.config.s3_region
18
+ end
19
+
20
+ def logger
21
+ @logger ||= S3DataPacker.logger
22
+ end
23
+
24
+ def each_key &block
25
+ bucket.objects(prefix: path).each do |item|
26
+ yield item.key
27
+ end
28
+ end
29
+
30
+ def exist?(key)
31
+ request! { object(key).exists? }
32
+ end
33
+
34
+ def download(key)
35
+ data = request! { object(key).get }
36
+ logger.warn "missing key #{key}" unless data
37
+ return nil unless data
38
+ data.body.read
39
+ end
40
+
41
+ def upload(file, opts={})
42
+ raise ArgumentError, 'File does not exist' unless File.exist?(file)
43
+ key = "#{path}/#{File.basename(file)}"
44
+ raise ArgumentError, "File #{File.basename(file)} already exists in target location" if exist?(key)
45
+ metadata = opts
46
+ metadata[:content_type] ||= file_mime_type(file)
47
+ metadata[:content_disposition] ||= 'attachement'
48
+ request! { object(key).upload_file(file, metadata) }
49
+ logger.info "Uploaded #{file} to s3://#{bucket_name}/#{key}"
50
+ end
51
+
52
+ private
53
+
54
+ def request! &block
55
+ begin
56
+ yield
57
+ rescue Aws::S3::Errors::InternalError
58
+ logger.warn "Aws::S3::Errors::InternalError, retrying in 1 second"
59
+ sleep(1)
60
+ retry
61
+ rescue Aws::S3::Errors::NoSuchKey
62
+ return nil
63
+ end
64
+ end
65
+
66
+ def file_mime_type(file)
67
+ begin
68
+ MIME::Types.type_for(file).first.content_type
69
+ rescue StandardError
70
+ logger.error "Could not guess MIME type of #{file}"
71
+ return nil
72
+ end
73
+ end
74
+
75
+ def object(key)
76
+ bucket.object(key)
77
+ end
78
+
79
+ def bucket
80
+ @bucket ||= resource.bucket(bucket_name)
81
+ end
82
+
83
+ def resource
84
+ @resource ||= ::Aws::S3::Resource.new(region: region, credentials: credentials)
85
+ end
86
+
87
+ end
88
+ end